copy from : https://note.qidong.name/2017/08/android-ninja/
如果說Makefile是一個DSL,那么Ninja就是一種配置文件。 本文簡單介紹Android中的Ninja。
Makefile與Ninja的對比
二者最核心的區別,在於設計哲學。 Makefile是設計來給人手寫的,而Ninja設計出來是給其它程序生成的。 如果說Makefile是C語言,那么Ninja就是匯編語言。 如果說Makefile是一個DSL,那么Ninja就是一種配置文件。 Makefile支持分支、循環等流程控制,而Ninja只支持一些固定形式的配置。
二者的相同點是,都是為了控制編譯流程而設計。 所以,他們的核心功能,都是指定目標,以及目標之間的依賴關系,自動計算執行順序。
與Makefile相比,由於Ninja僅僅專注於核心的功能,所以有輕巧、速度快的優點。
Makefile默認文件名為Makefile
或makefile
,也常用.make
或.mk
作為文件后綴。 Ninja的默認文件名是build.ninja
,其它文件也以.ninja
為后綴。 執行Makefile的程序,默認是GNU make,也有一些其它的實現。 Ninja的執行程序,就是ninja
命令。
在Android項目中,make
需要編譯主機上安裝,作為環境的一部分。 而ninja
命令則是Android平台代碼自帶。
$ find prebuilts/ -name ninja prebuilts/build-tools/linux-x86/asan/bin/ninja prebuilts/build-tools/linux-x86/bin/ninja prebuilts/build-tools/darwin-x86/bin/ninja
ninja命令的用法
通過ninja -h
,可以看到該命令的幫助文檔。
$ ninja -h usage: ninja [options] [targets...] if targets are unspecified, builds the 'default' target (see manual). options: --version print ninja version ("1.7.2") -C DIR change to DIR before doing anything else -f FILE specify input build file [default=build.ninja] -j N run N jobs in parallel [default=6, derived from CPUs available] -k N keep going until N jobs fail [default=1] -l N do not start new jobs if the load average is greater than N -n dry run (don't run commands but act like they succeeded) -v show all command lines while building -d MODE enable debugging (use -d list to list modes) -t TOOL run a subtool (use -t list to list subtools) terminates toplevel options; further flags are passed to the tool -w FLAG adjust warnings (use -w list to list warnings)
很多參數,和make
是比較類似的,比如-f
、-j
等,不再贅述。 有趣的是-t
、-d
、-w
這三個參數,最有用的是-t
。
$ ninja -t list ninja subtools: browse browse dependency graph in a web browser clean clean built files commands list all commands required to rebuild given targets deps show dependencies stored in the deps log graph output graphviz dot file for targets query show inputs/outputs for a path targets list targets by their rule or depth in the DAG compdb dump JSON compilation database to stdout recompact recompacts ninja-internal data structures
ninja -t clean
是清理產物,是自帶的,而make clean
往往需要自己實現。 其它都是查看編譯過程信息的工具,各有作用,可以進行復雜的編譯依賴分析。
Ninja的專注,在這里完全超越了Makefile。
Android中的Ninja文件
從Android 7開始,編譯時默認使用Ninja。 但是,Android項目里是沒有.ninja
文件的。 遵循Ninja的設計哲學,編譯時,會先把Makefile通過kati轉換成.ninja
文件,然后使用ninja
命令進行編譯。 這些.ninja
文件,都產生在out/
目錄下,共有三類。
一類是build-*.ninja
文件,通常非常大,幾十到幾百MB。 對make
全編譯,命名是build-<product_name>.ninja
。 如果Makefile發生修改,需要重新產生Ninja文件。
這里Android有一個bug,或者說設計失誤。 mm
、mma
的Ninja文件,命名是build-<product_name>-<path_to_Android.mk>.ninja
。 而mmm
、mmma
的Ninja文件,命名是build-<product_name>-_<path_to_Android.mk>.ninja
。 顯然,不同的單模塊編譯,產生的也是不同的Ninja文件。
這個設計本身就有一些問題了,為什么不同模塊不能共用一個總的Ninja文件? 這大概還是為了兼容舊的Makefile設計。 在某些Android.mk中,單模塊編譯與全編譯時,編譯內容截然不同。 如果說這還只能算是設計失誤的話,那么mm
與mmm
使用不同的編譯文件,就是顯然的bug了。 二者相差一個下划線_
,通過mv
或cp
,可以通用。
第二類是combined-*.ninja
文件。 在使用了Soong后,除了build-*.ninja
之外,還會產生對應的combined-*.ninja
,二者的*
內容相同。 以下以AOSP的aosp_arm64-eng為例,展示out/combined-aosp_arm64.ninja
文件的內容。
builddir = out
include out/build-aosp_arm64.ninja
include out/soong/build.ninja
build out/combined-aosp_arm64.ninja: phony out/soong/build.ninja
這類是組合文件,是把build-*.ninja
和out/soong/build.ninja
組合起來。 所以,使用Soong后,combined-*.ninja
是編譯執行的真正入口。
第三類是out/soong/build.ninja
文件,它是從所有的Android.bp
轉換過來的。
build-*.ninja
是從所有的Makefile,用Kati轉換過來的,包括build/core/*.mk
和所有的Android.mk
。 所以,在不使用Soong時,它是唯一入口。 在使用了Soong以后,會新增源於Android.bp
的out/soong/build.ninja
,所以需要combined-*.ninja
來組合一下。
可以通過以下命令,單獨產生全編譯的Ninja文件。
make nothing
用ninja編譯
在產生全編譯的Ninja文件后,可以繞過Makefile,單獨使用ninja
進行編譯。
全編譯(7.0版本),相當於make
:
ninja -f out/build-aosp_arm64.ninja
單獨編譯模塊,比如Settings,相當於make Settings
:
ninja -f out/build-aosp_arm64.ninja Settings
在8.0以上,上面的文件應該替換為out/combined-aosp_arm64.ninja
,否則可能找不到某些Target。
另外,還有辦法不用輸入-f
參數。 如前所述,如同Makefile之於make
,ninja
默認的編譯文件是build.ninja
。 所以,使用軟鏈接,可以避免每次輸入繁瑣的-f
。
ln -s out/combined-aosp_arm64.ninja build.ninja
ninja Settings
用ninja
進行單模塊編譯的好處,除了更快以外,還不用生成單模塊的Ninja文件,省了四五分鍾。
總結
在以Ninja在實際編譯中替換Makefile以后,Android在編譯時更快了一些。 不過,在首次生成、或重新生成Ninja文件時,往往額外耗時數分鍾,反而比原先使用Makefile更慢了。
在增量編譯方面,原先由於其Makefile編譯系統的實現問題,是不完善的。 也就是說,在make
編譯完一個項目后,如果再執行make
,會花費較長時間重新編譯部分內容。 而使用Ninja以后,增量編譯做得比較完善,第二次make
將在一分鍾內結束。
除此之外,由於Ninja的把編譯流程集中到了一個文件,並且提供了一些工具命令。 所以編譯信息的提取、編譯依賴的分析,變得更加方便了。