寫在開頭:本文為Android10.0編譯系統(tǒng)這個系列的筆記。
要站在巨人的肩膀上去學習,知識就在那里斋日,一個好的老師會讓你學得更快林艘,更好。我剛入門那會兒宪塔,上司教導我磁奖,不會沒有關系,要多借鑒他人的經驗某筐,見得多了比搭,自己多做總結就好了,記下來的就是自己的了南誊。大部分人的知識是都不是憑空而來的身诺,創(chuàng)新的畢竟是少數(shù)。
筆記的作用就是回憶的時候抄囚,找重點霉赡。每個人掌握的知識不一樣,關注點也不同幔托。記錄穴亏,可以讓下次快速找到某個知識點。畢竟搜索有時候并不可靠,由于信息有丟失嗓化,所以我都會注明來源锅劝。
本篇目錄結構如下:
- 1.概述
- 2.編譯構成
- 3.編譯步驟
- 4.main.mk
- 5.工具鏈的關系
- 6.system.img的打包
- 7.Kati
- 8.Blueprint
- 9.Ninja
- 10.android.mk的總結
1.概述
Ninja
Ninja是一個致力于速度的小型編譯系統(tǒng)(類似于Make),如果把其他編譯系統(tǒng)比做高級語言的話蟆湖,Ninja就是匯編語言故爵。通常使用Kati或soong把makefile轉換成Ninja files,然后用Ninja編譯隅津。
Ninja的誕生诬垂,主要是為了提升編譯速度,Ninja中去除了變量的計算伦仍,沒有默認規(guī)則结窘, 依賴必須顯式聲明,從而提升編譯速度充蓝∷矸悖基本上可以認為ninja就是make的最最精簡版。
Android.bp
Android.bp只是一個純粹的配置文件谓苟,不包括分支官脓、循環(huán)語句等控制流程,本質上就是一個json配置文件涝焙。Android.bp 通過Blueprint+soong轉換成ninja的構建規(guī)則文件build.ninja卑笨,再使用ninja來進行構建工作。
Android10.0上仑撞,mk和bp編譯的列表可以從 \out.module_paths中的Android.bp.list赤兴、Android.mk.list中看到,Android10.0還有400多個mk文件沒有被替換完隧哮,Google任重道遠桶良。
Android編譯演進過程:
Android7.0之前 使用GNU Make
Android7.0 引入ninja、kati、Android.bp和soong構建系統(tǒng)
Android8.0 默認打開Android.bp
Android9.0 強制使用Android.bp
2.編譯構成
Android的編譯目錄在/build 中,看一下Android 10源碼中的build目錄哼审,現(xiàn)在是這個樣子:
這個目錄中可以看到core文件夾被link到了make/core魔吐,envsetup.sh被link到make/envsetup.sh,這主要是為了對使用者屏蔽切換編譯系統(tǒng)的差異。
這里重點看四個文件夾:blueprint、kati、make瑰步、soong
在編譯過程中,Android.bp會被收集到out/soong/build.ninja.d,blueprint以此為基礎璧眠,生成out/soong/build.ninja
Android.mk會由kati/ckati生成為out/build-aosp_arm.ninja
兩個ninja文件會被整合進入out/combined-aosp_arm.ninja
3.編譯步驟
編譯會用到3條命令缩焦。
source build/envsetup.sh
lunch aosp_arm-eng // 或者 m PRODUCT-aosp_x86_64-eng 读虏,Android10.0不一定需要lunch命令
make -j8 //編譯模塊也可以直接用 m libart
-j8代表用系統(tǒng)的8個線程去編譯,因為一般不是單人使用所以不能太高袁滥,會影響其他人的使用盖桥。
加載envsetup.sh题翻,初始化一下環(huán)境變量揩徊。
envsetup.sh 主要做了下面幾個事情:
環(huán)境變量初始化完成后嵌赠,我們需要選擇一個編譯目標塑荒。lunch 主要作用是根據用戶輸入或者選擇的產品名來設置與具體產品相關的環(huán)境變量。eng表示工程版本词渤,一般調試編譯用userdebug把篓,正式版本用user口芍。
區(qū)別可看這篇: Android編譯選項eng、user裤翩、userdebug的區(qū)別
執(zhí)行完lunch命令后,就可以使用make命令來執(zhí)行編譯Build烫堤。
main.mk文件把一些環(huán)境變量和目標都配置好后荣赶,會執(zhí)行envsetup.sh中的make()進行編譯
4.main.mk
執(zhí)行runKatiBuild時,有個重要的步驟鸽斟,就是加載build/make/core/main.mk拔创,main.mk文件是Android Build系統(tǒng)的主控文件。從main.mk開始富蓄,將通過include命令將其所有需要的.mk文件包含進來剩燥,最終在內存中形成一個包括所有編譯腳本的集合,這個相當于一個巨大Makefile文件立倍。Makefile文件看上去很龐大灭红,其實主要由三種內容構成: 變量定義、函數(shù)定義和目標依賴規(guī)則口注,此外mk文件之間的包含也很重要变擒。
main.mk主要做了以下幾件事情:
1.定義編譯目標product
2.加載config.mk來初始化相關變量,檢測編譯環(huán)境和目標環(huán)境
3.清除規(guī)則寝志,清除out目錄中的dex文件
4.加載build/croe/definitions.mk,定義了很多通用函數(shù)娇斑,供編譯過程調用
5.加載平臺開發(fā)工具包 build/make/core/pdk_config.mk
6.加載系統(tǒng)中所有的Android.mk,最終會被存放到out/.module_paths/Android.mk.list
7.Link 類型檢查,校驗Link
8.要為此產品生成的模塊的基本列表由相應的產品定義文件指定材部,這些定義在"product_config.mk"中
9.運行時APEX庫毫缆,并進行檢查校驗
10.將所有要安裝的模塊都保存在變量ALL_DEFAULT_INSTALLED_MODULES中,并且將build/core/Makefie文件加載進來乐导。build/core/Makefie文件會根據要安裝的模塊生成system.img苦丁、super.img、boot.img和recovery.img等鏡像文件的生成規(guī)則
11.定義編譯的image目標
12.構建文件兽叮,然后將其打包成rom格式
5.工具鏈的關系
Android10.0的編譯系統(tǒng)中芬骄,涉及以下一些工具鏈猾愿,由這些工具鏈相輔相成,才最終編譯出了我們所需要的鏡像版本账阻。
Android10.0編譯工具鏈:
soong kati blueprint ninja
Soong
Soong 構建系統(tǒng)是在 Android 7.0 (Nougat) 中引入的蒂秘,旨在取代 Make。它利用 Kati GNU Make 克隆工具和 Ninja 構建系統(tǒng)組件來加速 Android 的構建淘太。
Soong是由Go語言寫的一個項目姻僧,從Android 7.0開始,在prebuilts/go/目錄下新增了Go語言所需的運行環(huán)境蒲牧,Soong在編譯時使用撇贺,解析Android.bp,將之轉化為Ninja文件冰抢,完成Android的選擇編譯松嘶,解析配置工作等。故Soong相當于Makefile編譯系統(tǒng)的核心挎扰,即build/make/core下面的內容翠订。
另外Soong還會編譯產生一個androidmk命令,可以用來手動將Android.mk轉換成Android.bp文件遵倦。不過這只對無選擇尽超、循環(huán)等復雜流程控制的Android.mk生效。
soong腳本和代碼目錄:/build/soong
kati
kati是一個基于Makefile來生成ninja.build的小項目梧躺。主要用于把Makefiel轉成成ninja file似谁,自身沒有編譯能力,轉換后使用Ninja編譯掠哥。
在編譯過程中巩踏,kati負責把既有的Makefile、Android.mk文件续搀,轉換成Ninja文件蛀缝。在Android 8.0以后,它與Soong一起目代,成為Ninja文件的兩大來源。Kati更像是Google過渡使用的一個工具嗤练,等所有Android.mk都被替換成Android.bp之后榛了,Kati有可能退出Android編譯過程.
在單獨使用時,它對普通的小項目還能勉強生效煞抬。面對復雜的霜大、多嵌套的Makefile時,它往往無法支持革答,會出現(xiàn)各種各樣的問題战坤。當然曙强,也可以理解為,它只為Android而設計途茫。
kati腳本和代碼目錄:/build/kati
Blueprint
Blueprint是生成碟嘴、解析Android.bp的工具,是Soong的一部分囊卜。Soong則是專為Android編譯而設計的工具娜扇,Blueprint只是解析文件的形式,而Soong則解釋內容的含義栅组。
ninja
最開始雀瓢,Ninja 是用于Chromium 瀏覽器中,Android 在SDK 7.0 中也引入了Ninja玉掸。
Ninja是一個致力于速度的小型編譯系統(tǒng)(類似于Make)刃麸,如果把其他編譯系統(tǒng)比做高級語言的話,Ninja就是匯編語言司浪。通常使用Kati或soong把makefile轉換成Ninja files泊业,然后用Ninja編譯。
主要兩個特點:
1)可以通過其他高級的編譯系統(tǒng)生成其輸入文件断傲;
2)它的設計就是為了更快的編譯脱吱;
ninja核心是由C/C++編寫的,同時有一部分輔助功能由python和shell實現(xiàn)认罩。由于其開源性箱蝠,所以可以利用ninja的開源代碼進行各種個性化的編譯定制。
從Android 7開始垦垂,編譯時默認使用Ninja宦搬。但是,Android項目里是沒有.ninja文件的劫拗。遵循Ninja的設計哲學间校,編譯時,會先把Makefile通過kati轉換成.ninja文件页慷,然后使用ninja命令進行編譯憔足。
Android.mk文件、Android.bp酒繁、kati滓彰、Soong、Blueprint州袒、Ninja之間的關系如下:
Android.bp --> Blueprint --> Soong --> Ninja
Makefile or Android.mk --> kati --> Ninja
(Android.mk --> Soong --> Blueprint --> Android.bp)
Blueprint是生成揭绑、解析Android.bp的工具,是Soong的一部分郎哭。Soong則是專為Android編譯而設計的工具他匪,Blueprint只是解析文件的形式菇存,而Soong則解釋內容的含義。
Android.mk可以通過Soong提供的androidmk轉換成Android.bp邦蜜,但僅限簡單配置依鸥。目前Oreo的編譯流程中,仍然是使用kati來做的轉換畦徘。
現(xiàn)存的Android.mk文件毕籽、既有的Android.bp,都會分別被轉換成Ninja井辆。
從Android.mk與其它Makefile关筒,會生成out/build-<product_name>.ninja文件。而從Android.bp杯缺,則會生成out/soong/build.ninja蒸播。此外,還會生成一個較小的out/combined-<product_name>.ninja文件萍肆,負責把二者組合起來袍榆,作為執(zhí)行入口。
最終塘揣,Ninja文件才是真正直接控制源碼編譯的工具包雀。
6.system.img的打包
main.mk中,最后兩步定義了需要編譯的image和構建一個rom的過程亲铡。
main.mk中只是做了一些定義和啟動編譯流程才写,正在的image打包在build/core/Makefile中完成。
在Makefile中奖蔓,.PHONY后面的target表示的也是一個偽造的target, 而不是真實存在的文件target赞草,注意Makefile的target默認是文件。如果有人依賴它吆鹤,就無條件執(zhí)行厨疙。
[build/core/Makefile]
...
.PHONY: systemimage
...
.PHONY: systemimage-nodeps snod
...
# Rules that need to be present for the all targets, even
# if they don't do anything.
.PHONY: systemimage
systemimage:
...
INSTALLED_SYSTEMIMAGE_TARGET := $(PRODUCT_OUT)/system.img
SYSTEMIMAGE_SOURCE_DIR := $(TARGET_OUT)
$(INSTALLED_SYSTEMIMAGE_TARGET): $(BUILT_SYSTEMIMAGE) $(RECOVERY_FROM_BOOT_PATCH)
@echo "Install system fs image: $@"
$(copy-file-to-target)
$(hide) $(call assert-max-image-size,$@ $(RECOVERY_FROM_BOOT_PATCH),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))
systemimage: $(INSTALLED_SYSTEMIMAGE_TARGET)
.PHONY: systemimage-nodeps snod
systemimage-nodeps snod: $(filter-out systemimage-nodeps snod,$(MAKECMDGOALS)) \
| $(INTERNAL_USERIMAGES_DEPS)
@echo "make $@: ignoring dependencies"
$(call build-systemimage-target,$(INSTALLED_SYSTEMIMAGE_TARGET))
$(hide) $(call assert-max-image-size,$(INSTALLED_SYSTEMIMAGE_TARGET),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))
...
關于system.img,這里定義了兩個偽目標systemimage 和 systemimage-nodeps疑务。
systemimage 依賴于INSTALLED_SYSTEMIMAGE_TARGET沾凄,最終生成目標文件
$(PRODUCT_OUT)/system.img
$(INSTALLED_SYSTEMIMAGE_TARGET): $(BUILT_SYSTEMIMAGE) $(RECOVERY_FROM_BOOT_PATCH)
@echo "Install system fs image: $@"
$(copy-file-to-target)
$(hide) $(call assert-max-image-size,$@ $(RECOVERY_FROM_BOOT_PATCH),$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))
INSTALLED_SYSTEMIMAGE_TARGET依賴于BUILT_SYSTEMIMAGE和RECOVERY_FROM_BOOT_PATCH,再調用了函數(shù)copy-file-to-target進行文件拷貝
最后調用build/make/tools/releasetools/build_image.py來生成system.img知允。
img格式是一種文件壓縮格式搭独。img就是把一些文件壓縮打包。
INTERNAL_USERIMAGES_DEPS:列出了制作system.img所需要的工具廊镜,例如out/host/linux-x86/bin/simg2img、out/host/linux-x86/bin/mkuserimg_mke2fs 等唉俗,如果支持f2fs的文件系統(tǒng)嗤朴,會加載out/host/linux-x86/bin/make_f2fs
INTERNAL_SYSTEMIMAGE_FILES:列出了制作system.img所需要的文件配椭,釋義如下:
ALL_GENERATED_SOURCES:描述的是要拷貝到目標設備上去的由工具自動生成的源代碼文件。
ALL_DEFAULT_INSTALLED_MODULES:描述的是所有需要安裝的module
PDK_FUSION_SYSIMG_FILES:是從PDK(Platform Development Kit)提取出來的相關文件
RECOVERY_RESOURCE_ZIP:描述的是Android的recovery系統(tǒng)要使用的資源文件雹姊,對應于/system/etc目錄下的recovery-resource.dat文件股缸。
PDK_FUSION_SYMLINK_STAMP:PDK的符號鏈接文件
7.Kati
Kati詳解-Android10.0編譯系統(tǒng)(五)
這篇文字不多,而且都是理論性的吱雏,因此不做提取敦姻。
8.Blueprint
Blueprint簡介-Android10.0編譯系統(tǒng)(六)
Blueprint
是一個meta-build系統(tǒng),它讀取描述需要構建的模塊的bp文件歧杏,并生成Ninja描述需要運行的命令及其依賴項的清單镰惦。
bluepring--soong的編譯過程經歷下面四個階段:
1.運行microfactory.bash以建立minibp -
2.運行.minibootstrap / build.ninja來構建.bootstrap / build.ninja -
3.運行.bootstrap / build.ninja來構建和運行主構建器 -
4.運行build.ninja來構建您的代碼
Blueprint文件是一個偽python數(shù)據格式的模塊列表,其中模塊類型看起來像函數(shù)調用犬绒,模塊的屬性看起來像可選參數(shù)旺入。例如,一個簡單的模塊可能看起來像:
cc_library {
name: "cmd",
srcs: [
"main.c",
],
deps: [
"libc",
],
}
subdirs = ["subdir1", "subdir2"]
格式說明:
[module type] {
name: "[name value]",
[property1 name]:"[property1 value]",
[property2 name]:"[property2 value]",
}
bp文件中的模塊(module) 以模塊類型(module type)開頭凯力,后面跟著一系列的屬性(property)茵瘾。每個模塊都必須具有一個屬性名為name的屬性,并且name的屬性值在所有Android.bp文件中必須是唯一的咐鹤。
更多編寫規(guī)則拗秘,可參考 :
Android 編譯之android.bp
Android.bp 語法淺析-Android10.0編譯系統(tǒng)(八)
基本上寫的時候,就是參照著源碼去寫祈惶。
Android系統(tǒng)中現(xiàn)在基本是bp和mk文件混著用雕旨。bp一般要搭配go腳本來達到宏控制的目的。Android.bp 文件很簡單行瑞。它們不包含任何條件語句奸腺,也不包含控制流語句;所有復雜問題都由用 Go 編寫的構建邏輯處理血久。
具體可參考這篇:
Android.bp正確姿勢添加宏控制編譯指南
這個博主寫的系列文章也都很不錯突照,細節(jié)性很強。
//xxxparser.go
package xxxparser
import (
"android/soong/android"
"android/soong/cc"
)
func init() {
// resister a module "xxxparser_defaults"
android.RegisterModuleType("xxxparser_defaults", xxxdroidDefaultsFactory)
}
func xxxdroidDefaultsFactory() (android.Module) {
module := cc.DefaultsFactory()
android.AddLoadHook(module, xxxdroidDefaults)
return module
}
func xxxdroidDefaults(ctx android.LoadHookContext) {
type props struct {
Cflags []string
}
p := &props{}
p.Cflags = globalDefaults(ctx)
ctx.AppendProperties(p)
}
func globalDefaults(ctx android.BaseContext) ([]string) {
var cppflags []string
if ctx.AConfig().Getenv("ANDROIDBP_FUN") == "YES" {
cppflags = append(cppflags,"-DXXX")
}
return cppflags
}
在Android.bp開頭位置引入go腳本文件xxxparser.go氧吐,如下:
//引入go腳本
bootstrap_go_package {
name: "soong-xxxparser",
pkgPath: "android/soong/xxxparser",
deps: [
"blueprint",
"blueprint-pathtools",
"soong",
"soong-android",
"soong-cc",
"soong-genrule",
],
srcs: [
"xxxparser.go",
],
pluginFor: ["soong_build"],
}
xxxparser_defaults {
name: "xxxparser_defaults",
}
ANDROIDBP_FUN = ["YES"]
cc_binary {
defaults: ["xxxparser_defaults"],
name: "AndroidBp",
srcs: ["main.c"],
cflags: ["-Wno-error=implicit-function-declaration"],
shared_libs: [
"libcutils",
"liblog",
"libutils",
],
}
以上語句可以保證運行Android.bp時讹蘑,先編譯對應的xxxparser.go運行go腳本時,會首先運行init函數(shù)筑舅,將 xxxdroidDefaultsFactory函數(shù)注冊到module中座慰,之后調用xxxdroidDefaultsFactory函數(shù)時,會將回調函數(shù) xxxdroidDefaults注冊進去之后調用 privateParserDefaults 時翠拣,我們可以從 ctx.AConfig() 中獲取好多屬性
(參考 build/soong/android/config.go 中對 build/soong/android/module.go中的 androidBaseContext interface的各種函數(shù)實現(xiàn))版仔,其中有一項是獲取宏值的,之后回調xxxdroidDefaults添加宏信息。
9.Ninja
Ninja簡介-Android10.0編譯系統(tǒng)(九)
Ninja提升編譯速度的方法-Android10.0編譯系統(tǒng)(十)
Ninja
是一個編譯框架蛮粮,會根據相應的ninja格式的配置文件進行編譯益缎,但是ninja文件一般不會手動修改,而是通過將Android.bp文件轉換成ninja格文件來編譯然想。
Ninja是一個注重速度的小型構建系統(tǒng)莺奔。它與其他構建系統(tǒng)在兩個主要方面不同:它被設計為使其輸入文件由更高級別的構建系統(tǒng)生成,并且被設計為盡可能快地運行構建变泄。
編譯分析
從Android O開始令哟,soong已經是google的入口。從soong入口后妨蛹,會經soong_ui,soong,kati,blueprint幾個階段屏富,把mk,bp轉換成ninja文件后滑燃,然后執(zhí)行ninja命令解析ninja文件進行編譯役听。
如下圖所示整個編譯過程,準備過程非常冗長表窘。
每次編譯都要重新收集所有的文件典予、.mk、.bp的修改乐严,然后重新生成build.ninja瘤袖,在合并成combined-aosp_arm.ninja。
大部分情況下昂验,研發(fā)的工作是不斷的修改.c .h .cpp .java 然后增量捂敌,此時真正的編譯工作是非常少的,這樣相對而言既琴,準備工作往往是占大頭的占婉,所以我們可以考慮舍棄combined-aosp_arm.ninja之前的準備過程。
這里以增量編譯init_system為例甫恩,之前我們已經編好了init_system逆济,然后如果我們繼續(xù)用m命令單編init_system,需要2分鐘磺箕。
ingresge:~/AP/AOSP_Q$ time m init_system
[100% 6336/6336] Install: out/target/product/generic/fake_packages/init_system-timestamp
#### build completed successfully (02:37 (mm:ss)) ####
real 2m36.672s
user 43m51.510s
sys 2m51.991s
為了對比編譯時間奖慌,我們直接拋棄了編譯的環(huán)境和ninja文件生成的逐步過程,我們使用下面的命令直接跑ninja松靡,結果只花了5秒简僧。
命令:
time prebuilts/build-tools/linux-x86/bin/ninja -v -d keepdepfile init_system -f out/combined-aosp_arm.ninja -w dupbuild=err
編譯結果:
ingresge:~/AP/AOSP_Q$ time prebuilts/build-tools/linux-x86/bin/ninja -v -d keepdepfile init_system -f out/combined-aosp_arm.ninja -w dupbuild=err
[7/7] /bin/bash -c "(rm -f out/target/product/generic/system/bin/init ) && (cp out/target/product/generic/obj/EXECUTABLES/init_second_stage_intermediates/init out/target/product/generic/system/bin/init )"
real 0m5.351s
user 0m14.752s
sys 0m3.201s
這種命令可以寫到sh腳本中去,方便快速執(zhí)行。
在修改build/make/envsetup.sh雕欺,新增一個qninja函數(shù)岛马。
function qninja()
{
local cmdline="time prebuilts/build-tools/linux-x86/bin/ninja -v -d keepdepfile $@ -f out/combined-aosp_arm.ninja -w dupbuild=warn"
echo $cmdline
$cmdline
}
只是修改了某個模塊中的.c .h .cpp .java后棉姐,進行增量,編譯命令如下:
source build/envsetup.sh
qninja init_system
加載配置文件蛛枚,執(zhí)行qninja函數(shù)谅海。
早期可以是mm單編某個應用提升編譯效率,現(xiàn)在就可以使用Ninja了蹦浦。
編譯Settings
./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-xxx.ninja Settings -j32
編譯selinux
./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-xxx.ninja selinux_policy -j32
編譯Framework
./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-xxx.ninja framework -j32
全編譯
./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-xxx.ninja -j32 2>&1 |tee ninja_build.log
10.android.mk的總結
android.mk的使用實例介紹看這篇。
Android 編譯之android.mk
文章中介紹了以下幾種mk的編寫撞蜂。
參考鏈接:
編譯系統(tǒng)入門篇-Android10.0編譯系統(tǒng)(一)
Android編譯系統(tǒng)簡要介紹和學習計劃
從CM刷機過程和原理分析Android系統(tǒng)結構
Android編譯系統(tǒng)分析五:system.img的生成過程
Makefile中.PHONY的作用
Android系統(tǒng)快速編譯方式ninja
Makefile概念入門
Android 編譯之android.mk