Makefile結構分析
咱們以chaos calmer
的代碼爲例,整個編譯的入口是在源碼根目錄下的Makefile。編譯的各類命令都應該在源碼根目錄下鍵入。
整個主Makefile的結構以下:
world:
ifneq ($(OPENWRT_BUILD),1)
頂層
else
第二層
endif
開始部分是一些註釋和變量定義及路徑檢查。
根據Makefile的規則,在沒有指定編譯目標的時候,Makefile中的第一個目標將做爲默認目標。
換句話說,當咱們執行make V=s
時,這個時候編譯的目標就是world
.和咱們執行make world V=s
效果是同樣的。
頂層
一般在編譯時,咱們不會定義變量OPENWRT_BUILD
的值,因此一般咱們是會走到頂層的。
頂層代碼以下:
_SINGLE=export MAKEFLAGS=$(space);
override OPENWRT_BUILD=1
export OPENWRT_BUILD
GREP_OPTIONS=
export GREP_OPTIONS
include $(TOPDIR)/include/debug.mk
include $(TOPDIR)/include/depends.mk
include $(TOPDIR)/include/toplevel.mk
這裏咱們看到變量OPENWRT_BUILD
被置爲1,而後包含了3個.mk
文件。
這裏稍微解釋下.mk
文件,它們通常沒有什麼執行動做,都是一些變量的定義還有依賴關係的說明,能夠類比於C語言的頭文件來理解。
debug.mk:
能夠經過定義DEBUG的值來控制編譯過程。
depends.mk:
主要定義了rdep這個變量。
toplevel.mk:
這個是咱們跟蹤編譯過程的重要的文件,這個文件在源碼根目錄下的
include
文件夾下。
核心代碼以下:
1 %:: 2 @+$(PREP_MK) $(NO_TRACE_MAKE) -r -s prereq 3 @( \ 4 cp .config tmp/.config; \ 5 ./scripts/config/conf --defconfig=tmp/.config -w tmp/.config Config.in > /dev/null 2>&1; \ 6 if ./scripts/kconfig.pl '>' .config tmp/.config | grep -q CONFIG; then \ 7 printf "$(_R)WARNING: your configuration is out of sync. Please run make menuconfig, oldconfig or defconfig!$(_N)\n" >&2; \ 8 fi \ 9 ) 10 @+$(ULIMIT_FIX) $(SUBMAKE) -r $@ $(if $(WARN_PARALLEL_ERROR), || { \ 11 printf "$(_R)Build failed - please re-run with -j1 to see the real error message$(_N)\n" >&2; \ 12 false; \ 13 } )
除了少數在toplevel中被定義的目標外,其餘編譯目標都會走到這裏,將之簡化後(執行命令爲: make V=s):
%:: @make V=s -r -s prereq @make -w -r world
首先執行prereq
,而後再執行咱們指定的目標或者默認目標world
。
prereq整理後的依賴關係以下:
其中,staging_dir/host/.prereq-build:
將會執行一系列主機檢查,是否安裝了必要的軟件。
prepare-tmpinfo:
根據scan.mk,掃描
target/linux
和package
目錄,生成packageinfo和targetinfo。
總之,頂層完成一系列必要的準備工做.對於絕大多數的目標而言,頂層是必經之路。固然,在toplevel.mk
中,咱們也能夠看到目標menuconfig
。也就是說對於目標menuconfig
而言,將不會執行到第二層的邏輯。
第二層
在上面執行完make prereq
以後,將執行make world。
還記得咱們進入頂層後修改了變量OPENWRT_BUILD
麼?當再次執行make world
的時候,因爲條件不知足,咱們將直接進入第二層來執行。
include rules.mk
include $(INCLUDE_DIR)/depends.mk
include $(INCLUDE_DIR)/subdir.mk
include target/Makefile
include package/Makefile
include tools/Makefile
include toolchain/Makefile
rules.mk:
很重要的一個mk文件,其中規定了不少有用的變量,包括各類目錄路徑的定義,交叉編譯器等等。其中:
ifeq ($(DUMP),) -include $(TOPDIR)/.config endif
就是包含了咱們的配置文件。對於
Makefile
而言,.config
文件就是一大串變量的定義,Makefile能夠直接讀取這些定義,從而控制編譯過程。
subdir.mk:
這個是讀懂咱們整個編譯過程的關鍵所在,其中主要定義了兩個函數:subdir和stampfile,咱們稍後加以解釋。
接下來,包含了4個Makefile文件。咱們以target/Makefile
爲例.該文件位於target
目錄下。
核心部分爲:
$(eval $(call stampfile,$(curdir),target,prereq,.config))
$(eval $(call stampfile,$(curdir),target,compile,$(TMP_DIR)/.build))
$(eval $(call stampfile,$(curdir),target,install,$(TMP_DIR)/.build))
$(eval $(call subdir,$(curdir)))
這裏調用了subdir.mk
中定義的stampfile
函數。將會生成target/stamp-prereq
,target/stamp-compile
,target/stamp-install
三個變量。
以target/stamp-prereq
爲例,執行部分爲make target/prereq
。同理target/stamp-compile
,執行部分爲make target/compile
。
最後又調用了sbudir
函數,這個函數規定了目標和各子文件夾之間的依賴關係。若是有必定的Makefile基礎能夠去讀讀subdir.mk
文件。
舉例而言就是:
當執行目標爲
target/compile
,這個目標將依賴於target/linux/compile
。
當執行目標爲package/compile
,這個目標將依賴於package
目錄下各子文件夾的compile
。
下面就是規定了一系列的依賴關係,我已經將他們梳理了出來,以下圖:
編譯過程當中的一些變量可能會形成一些困擾,這裏將它們的真實值記錄下來,以執行make V=s爲例:
1 $(PREP_MK) => OPENWRT_BUILD= QUIET=0 2 3 $(NO_TRACE_MAKE) => make V=ss 4 5 $(_SINGLE) => export MAKEFLAGS= ; 6 7 $(ULIMIT_FIX) => _limit=1024; [ = unlimited -o -ge 1024 ] || ulimit -n 1024;