簡單的Android.mk運行解析

平常的一個編譯Shared library的Android.mk會像下面這樣

LOCAL_PATH := $(call my-dir)
#   呼叫my-dir這個定義, 將變數LOCAL_PATH設置為當前Android.mk所在的目錄
include $(CLEAR_VARS)
#   引入CLEAR_VARS這個mk檔案, 用來重置/清除編譯系統內定的變數群
LOCAL_MODULE := hello-jni
#   指定準備要編譯的模組名稱, 整個要編譯的workspace不能有相同的模組名稱
LOCAL_SRC_FILES := hello-jni.c
#   指定編譯模組時使用的程式碼檔案
include $(BUILD_SHARED_LIBRARY)
#   引入BUILD_SHARED_LIBRARY mk檔案, 指定編譯模組成為動態函數庫(.so)

這些實際上使用的都是makefile語法來處理,只是AOSP內有很多方便的基礎定義,所以看起來和makefile不一樣

:=是將變數設值的意思, 使用變數要寫$(變數名稱),呼叫函數也是寫$(函數名稱)
呼叫函數用法是$(<function-name> <arguments>)
像是$(info Print a hello world)就會將Print a hello world印在螢幕上
$(warning show your warning message)會將show your warning message印在錯誤輸出上
$(error show your error message)會將show your error message印在錯誤輸出上,然後結束執行
參考:https://www.gnu.org/software/make/manual/html_node/Make-Control-Functions.html

$(call XXXX)是呼叫某個定義,可以帶入多組參數,參數用逗號分隔
在makefile中用法是$(call <expression>,<parm1>,<parm2>,<parm3>,...)
expression裡面可以寫很複雜得判斷,而後面的param系列都是要帶入給expression用的參數
而expression的返回值就是call函數的返回值。
在expression中寫$(1)是指parm1, $(2)是指parm2,以此類推

一個簡單的定義就像下面這樣,將參數1和參數2反過來
#定義反置
define my-reverse
$(2) $(1)
endef
#使用定義
foo := $(call my-reverse,a,b)
$(info print the content of foo $(foo))
會得到螢幕輸出"print the content of foo b a"

my-dir定義在build/core/definitions.mk,裡面有相當多定義可以看看
define my-dir
$(strip \
  $(eval LOCAL_MODULE_MAKEFILE := $$(lastword $$(MAKEFILE_LIST))) \
  $(if $(filter $(BUILD_SYSTEM)/% $(OUT_DIR)/%,$(LOCAL_MODULE_MAKEFILE)), \
    $(error my-dir must be called before including any other makefile.) \
   , \
    $(patsubst %/,%,$(dir $(LOCAL_MODULE_MAKEFILE))) \
   ) \
 )
endef
使用define和endef結尾來產生定義。然而因為makefile中多行指令是分別運行在不同的shell下的,無法取得上一個運行的結果,也就難進行條件判斷等功能,所以都AOSP提供的很多定義都寫成一行,但是寫成一行很難閱讀,所以倚靠parser 讀取到結尾\字元時,將內容串連起來。

strip是去字串頭尾空白的函數,用法:$(strip <string>)
eval跟bash那個一樣有點複雜,請參考:https://www.gnu.org/software/make/manual/html_node/Eval-Function.html
lastword就是取空白分隔的最後一個參數, 用法:$(lastword names…)
MAKEFILE_LIST是makefile的特殊變數, 為makefile已parse的檔案列表, https://www.gnu.org/software/make/manual/html_node/Special-Variables.html
兩個$字號會被視為是一個$的字母,並且避免被置換成變數或呼叫函數,隨後在透過eval執行,將所需的內容變成實際結果
if 就是判斷式,用法: $(if <condition>,<then-part>)或者$(if <condition>,<then-part>,<else-part>)
filter是找出符合pattern的文字,%是萬用字元,用法:$(filter pattern…,text)
patsubst是pattern substitution的意思, %也是萬用字元,用法:$(patsubst pattern,replacement,text)
dir是取得字串的目錄部分, 結果含有/,用法:$(dir names…)

include是makefile內建的指令(directive),是引用其他makefile的功能時用,在include後的文字會先展開後才進行引用,寫法是include filenames…
例如:include foo *.mk bish bash,其中的*.mk就會先被展開成當下目錄的任意名.mk檔
請參考: https://www.gnu.org/software/make/manual/html_node/Include.html

CLEAR_VARS和BUILD_SHARED_LIBRARY在build\core\config.mk中為兩個變數
CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
由前面知道include會先展開參數,所以也就是引用build\core\clear_vars.mk和build\core\shared_library.mk的意思

clear_vars.mk主要在清除LOCAL_開頭的變數
shared_library.mk則是在協助使用者設定makefile的編譯規則

參考資料
1.Makefile基礎整理 https://dotblogs.com.tw/kent2480/2014/01/09/139354
2. Ubuntu wiki:跟我一起寫Makefile:使用函數
http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:%E4%BD%BF%E7%94%A8%E5%87%BD%E6%95%B0
3. Makefile所提供的字串函數用法(英文) https://www.gnu.org/software/make/manual/html_node/Text-Functions.html
4. $@, $<之類的變數
https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html


留言