(三)openwrt主Makefile解析


本周成胖子每周一博到了第四周^_^

前言

前一篇,我們大概描述了整個鏡像文件的生成過程.本周我們來解析主Makefile,看看主要編譯過程是怎么產生的.

主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文件夾下.

核心代碼如下:

%::
    @+$(PREP_MK) $(NO_TRACE_MAKE) -r -s prereq
    @( \
        cp .config tmp/.config; \
        ./scripts/config/conf --defconfig=tmp/.config -w tmp/.config Config.in > /dev/null 2>&1; \
        if ./scripts/kconfig.pl '>' .config tmp/.config | grep -q CONFIG; then \
            printf "$(_R)WARNING: your configuration is out of sync. Please run make menuconfig, oldconfig or defconfig!$(_N)\n" >&2; \
        fi \
    )
    @+$(ULIMIT_FIX) $(SUBMAKE) -r $@ $(if $(WARN_PARALLEL_ERROR), || { \
        printf "$(_R)Build failed - please re-run with -j1 to see the real error message$(_N)\n" >&2; \
        false; \
    } )

除了少數在toplevel中被定義的目標外,其他編譯目標都會走到這里.將之簡化后:

%::
    make prereq
    make $@

首先執行prereq,然后再執行我們指定的目標或者默認目標world.
prereq整理后的依賴關系如下:
prereq
其中
staging_dir/host/.prereq-build:

將會執行一系列主機檢查,是否安裝了必要的軟件.

prepare-tmpinfo:

根據scan.mk,掃描target/linuxpackage目錄,生成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:

這個是讀懂我們整個編譯過程的關鍵所在,其中主要定義了兩個函數:subdirstampfile,我們稍后加以解釋.

接下來,包含了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.

下面就是規定了一系列的依賴關系,我已經將他們梳理了出來,如下圖:
world

這里就是第二層解析后的依賴關系.當依賴關系生成后,將會從最先被依賴的目標開始執行.
比如我們可以看到進入第二層后,tools/stamp-install將會最先被執行,也就是主機工具將會最先被編譯,安裝.我們上一篇提高的整個編譯過程能從上圖中得出.

尾記

  1. 想要讀懂Makefile,首先要梳理各個依賴關系.而要梳理各個依賴關系,關鍵要關注冒號和make -C
  2. 本周我們解析了主Makefile,在Makefile的執行過程中要理解make的執行過程.先讀入Makefile,然后構建依賴關系,最后最先被依賴的目標將會先執行.
  3. 我主要描繪了主要枝干,如果希望了解更多細節,還是要自己去閱讀Makefile.
  4. 接下來兩篇,我們將主要分析下,和我們開發者比較相關的兩個目標的執行過程:package/stamp-compiletarget/stamp-install.下周再會^_^

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM