1. 程式人生 > >Android編譯系統分析三:make完整編譯android系統

Android編譯系統分析三:make完整編譯android系統

Android編譯系統分析系列文章:


這篇部落格的目標是摸清楚預設編譯整個android系統時程式碼的流程。

當我們執行make的時候,會查詢當前的Makefie檔案或者makefile檔案並且執行,在android頂級原始碼目錄下面,確實有個Makefile,它之後一行內容:

### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
因此,正真執行的是build/core/main.mk

一.依賴淺析

當我們執行make命令的時候,如果沒有傳入一個目標,那麼就會執行預設的目標。注意,我們在編譯android系統的時候,只需要執行make就可以了,那麼很顯然它會執行預設的目標了,那麼預設的目標是什麼呢?

在build/core/main.mk中:

# This is the default target.  It must be the first declared target.
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL):

在main.mk開始不久,就出現了一個偽目標,即便你看不懂Makefile也沒有關係,註釋上說的很清楚了,他就是預設的目標了。而且這個預設的目標是一個偽目標。make工具遇到偽目標以後,會檢查解析偽目標的依賴,如果偽目標存在依賴,就會檢查這些依賴,如果這些依賴是偽目標,繼續檢查這個偽目標的依賴,如果不是偽目標,就會生成這個目標。

閱讀一個Makefile,理清目標的依賴關係很重,下圖列出了部分重要的以來關係:


在對依賴關係有個瞭解之後,我們開始順著make的載入流程,看看它到底做了什麼。

首先,我覺得很重要的就是載入特定產品的配置資訊。

二.配置產品資訊

首先,大致的流程如下圖所示:


在product_config.mk中:

ifneq ($(strip $(TARGET_BUILD_APPS)),)
# An unbundled app build needs only the core product makefiles.
all_product_configs := $(call get-product-makefiles,\
    $(SRC_TARGET_DIR)/product/AndroidProducts.mk)
else
# Read in all of the product definitions specified by the AndroidProducts.mk
# files in the tree.
all_product_configs := $(get-all-product-makefiles)
endif
1.AndoridProducts.mk 使用get-all-product-makefiles獲取所有的AndoridProducts.mk檔案:
define get-all-product-makefiles
$(call get-product-makefiles,$(_find-android-products-files))
endef
呼叫_find-android-products-files獲取所有的AndroidProducts.mk,然後交由get-product-makefiles函式處理。
define _find-android-products-files
$(shell test -d device && find device -maxdepth 6 -name AndroidProducts.mk) \
  $(shell test -d vendor && find vendor -maxdepth 6 -name AndroidProducts.mk) \
  $(SRC_TARGET_DIR)/product/AndroidProducts.mk
endef
define get-product-makefiles
$(sort \
  $(foreach f,$(1), \
    $(eval PRODUCT_MAKEFILES :=) \
    $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
    $(eval include $(f)) \
    $(PRODUCT_MAKEFILES) \
   ) \
  $(eval PRODUCT_MAKEFILES :=) \
  $(eval LOCAL_DIR :=) \
 )
endef
可以看到最終處理的結果是載入了AndroidProducts.mk, 返回了一個排好順序的PRODUCT_MAKEFILES。

這裡把所有的AndroidProducts.mk都載入進來了,但是我們只需要我們產品的配置資訊呀,所以接著做一個查詢,找到屬於我們產品的AndroidProducts.mk:

# Find the product config makefile for the current product.
# all_product_configs consists items like:
# <product_name>:<path_to_the_product_makefile>
# or just <path_to_the_product_makefile> in case the product name is the
# same as the base filename of the product config makefile.
current_product_makefile :=
all_product_makefiles :=
$(foreach f, $(all_product_configs),\
    $(eval _cpm_words := $(subst :,$(space),$(f)))\
    $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\
    $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\
    $(if $(_cpm_word2),\
        $(eval all_product_makefiles += $(_cpm_word2))\
        $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\
            $(eval current_product_makefile += $(_cpm_word2)),),\
        $(eval all_product_makefiles += $(f))\
        $(if $(filter $(TARGET_PRODUCT),$(basename $(notdir $(f)))),\
            $(eval current_product_makefile += $(f)),)))
_cpm_words :=
_cpm_word1 :=
_cpm_word2 :=
current_product_makefile := $(strip $(current_product_makefile))
all_product_makefiles := $(strip $(all_product_makefiles))

2.current_product_makefile

最終找到的結果儲存在current_product_makefile中。關於它的值,這裡舉例說明:

加入我們在lunch的時候選擇了 5:

     1. aosp_arm-eng
     2. aosp_arm64-eng
     3. aosp_mips-eng
     4. aosp_mips64-eng
     5. aosp_x86-eng
     6. aosp_x86_64-eng
那麼經過以上查詢current_product_makefile就等於device/generic/x86/mini_x86.mk 3.載入產品配置檔案
ifneq (,$(filter product-graph dump-products, $(MAKECMDGOALS)))
# Import all product makefiles.
$(call import-products, $(all_product_makefiles))
else
# Import just the current product.
ifndef current_product_makefile
$(error Can not locate config makefile for product "$(TARGET_PRODUCT)")
endif
ifneq (1,$(words $(current_product_makefile)))
$(error Product "$(TARGET_PRODUCT)" ambiguous: matches $(current_product_makefile))
endif
$(call import-products, $(current_product_makefile))
endif  # Import all or just the current product makefile

# Sanity check
$(check-all-products)
在import-products中匯入產品的配置資訊,這裡就是device/generic/x86/mini_x86.mk。

4然後獲取TARGET_DEVICE的值:
# Find the device that this product maps to.
TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)
此時,TARGET_DEVICE = mini_x86.mk;

5獲取要拷貝的檔案

# A list of words like <source path>:<destination path>[:<owner>].
# The file at the source path should be copied to the destination path
# when building  this product.  <destination path> is relative to
# $(PRODUCT_OUT), so it should look like, e.g., "system/etc/file.xml".
# The rules for these copy steps are defined in build/core/Makefile.
# The optional :<owner> is used to indicate the owner of a vendor file.
PRODUCT_COPY_FILES := \
    $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_COPY_FILES))

這個變數也很重要,它儲存了需要拷貝的檔案。格式為 <source path>:<destination path>,在build/core/Makefile一開始就會先拷貝這個變數指定的檔案。

6.載入BoardConfig.mk

又回到envsetup.mk中:

# Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)
# or under vendor/*/$(TARGET_DEVICE).  Search in both places, but
# make sure only one exists.
# Real boards should always be associated with an OEM vendor.
board_config_mk := \
	$(strip $(wildcard \
		$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
		$(shell test -d device && find device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
		$(shell test -d vendor && find vendor -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
	))
ifeq ($(board_config_mk),)
  $(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))
endif
ifneq ($(words $(board_config_mk)),1)
  $(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))
endif
include $(board_config_mk)
ifeq ($(TARGET_ARCH),)
  $(error TARGET_ARCH not defined by board config: $(board_config_mk))
endif

BoardConfig.mk中配置了重要的板級資訊,比如cpu架構等。

至此,配置一個產品所需的AndroidProducts.mk,具體產品的配置檔案,比如這裡的mini_x86.mk以及BoardConfig.mk都載入進來了。


三.載入所有模組

載入完單板資訊,make又回到main.mk中,不就就發現了ONE_SHOT_MAKEFILE變數的判斷:

1ONE_SHOT_MAKEFILE

ifneq ($(ONE_SHOT_MAKEFILE),)
# We've probably been invoked by the "mm" shell function
# with a subdirectory's makefile.
include $(ONE_SHOT_MAKEFILE)
# Change CUSTOM_MODULES to include only modules that were
# defined by this makefile; this will install all of those
# modules as a side-effect.  Do this after including ONE_SHOT_MAKEFILE
# so that the modules will be installed in the same place they
# would have been with a normal make.
CUSTOM_MODULES := $(sort $(call get-tagged-modules,$(ALL_MODULE_TAGS)))
FULL_BUILD :=
# Stub out the notice targets, which probably aren't defined
# when using ONE_SHOT_MAKEFILE.
NOTICE-HOST-%: ;
NOTICE-TARGET-%: ;

# A helper goal printing out install paths
.PHONY: GET-INSTALL-PATH
GET-INSTALL-PATH:
	@$(foreach m, $(ALL_MODULES), $(if $(ALL_MODULES.$(m).INSTALLED), \
		echo 'INSTALL-PATH: $(m) $(ALL_MODULES.$(m).INSTALLED)';))

else # ONE_SHOT_MAKEFILE

ifneq ($(dont_bother),true)
#
# Include all of the makefiles in the system
#

# Can't use first-makefiles-under here because
# --mindepth=2 makes the prunes not work.
subdir_makefiles := \
	$(shell build/tools/findleaves.py --prune=$(OUT_DIR) --prune=.repo --prune=.git $(subdirs) Android.mk)

$(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))

endif # dont_bother

endif # ONE_SHOT_MAKEFILE
如果這個變數定義了,那麼,就是編譯一個模組,在上一篇部落格中已將分析過了,如果沒有定義,就說明是編譯整個系統。
MAKECMDGOALS是make的一個環境變數,當我們執行make的時候並沒有設定它,因此它為空。所以dont_bother不等於true,因此,就會載入所有的Android.mk.這裡使用
一個python指令碼查詢系統中所有的Android.mk,然後Include進來。
四 收集所有要安裝的模組
在main.mk中繼續往下看:

3.1FULL_BUILD

ifdef FULL_BUILD
  # The base list of modules to build for this product is specified
  # by the appropriate product definition file, which was included
  # by product_config.mk.
  product_MODULES := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES)
  # Filter out the overridden packages before doing expansion
  product_MODULES := $(filter-out $(foreach p, $(product_MODULES), \
      $(PACKAGES.$(p).OVERRIDES)), $(product_MODULES))

  # Resolve the :32 :64 module name
  modules_32 := $(patsubst %:32,%,$(filter %:32, $(product_MODULES)))
  modules_64 := $(patsubst %:64,%,$(filter %:64, $(product_MODULES)))
  modules_rest := $(filter-out %:32 %:64,$(product_MODULES))
  # Note for 32-bit product, $(modules_32) and $(modules_64) will be
  # added as their original module names.
  product_MODULES := $(call get-32-bit-modules-if-we-can, $(modules_32))
  product_MODULES += $(modules_64)
  # For the rest we add both
  product_MODULES += $(call get-32-bit-modules, $(modules_rest))
  product_MODULES += $(modules_rest)

  $(call expand-required-modules,product_MODULES,$(product_MODULES))

  product_FILES := $(call module-installed-files, $(product_MODULES))
  ifeq (0,1)
    $(info product_FILES for $(TARGET_DEVICE) ($(INTERNAL_PRODUCT)):)
    $(foreach p,$(product_FILES),$(info :   $(p)))
    $(error done)
  endif
else
  # We're not doing a full build, and are probably only including
  # a subset of the module makefiles.  Don't try to build any modules
  # requested by the product, because we probably won't have rules
  # to build them.
  product_FILES :=
endif
在執行make的時候,FULL_BUILD:=true
product_MODULES是所有產品配置檔案中新增的要打包進系統映象中的模組,它只是一個名字,比如上篇部落格分析過的screencap。
product_FILES獲取對應模組的.INSTALLED的值。
define module-installed-files
$(foreach module,$(1),$(ALL_MODULES.$(module).INSTALLED))
endef
在載入單個模組的時候,會給每一個模組生成另外兩個值:
$(ALL_MODULES.$(target)).BUILT
$(ALL_MODULES.$(target)).INSTALLED
它們在base_rule.mk中生成:
ALL_MODULES.$(my_register_name).BUILT := \
    $(ALL_MODULES.$(my_register_name).BUILT) $(LOCAL_BUILT_MODULE)
ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
ALL_MODULES.$(my_register_name).INSTALLED := \
    $(strip $(ALL_MODULES.$(my_register_name).INSTALLED) $(LOCAL_INSTALLED_MODULE))
ALL_MODULES.$(my_register_name).BUILT_INSTALLED := \
    $(strip $(ALL_MODULES.$(my_register_name).BUILT_INSTALLED) $(LOCAL_BUILT_MODULE):$(LOCAL_INSTALLED_MODULE))
endif

$(ALL_MODULES.$(target)).BUILT代表的一般是out/target/product/xxx/obj下編譯生成的模組。
$(ALL_MODULES.$(target)).INSTALLED代表的是out/target/product/xxx/system下生成的模組。

3.2 全部安裝模組

modules_to_install := $(sort \
    $(ALL_DEFAULT_INSTALLED_MODULES) \
    $(product_FILES) \
    $(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \
    $(CUSTOM_MODULES) \
  )

ALL_DEFAULT_INSTALLED_MODULES是系統預設要安裝的模組,product_FILES是特定產品附加的要安裝的模組,foreach找到的是特定
TAG的模組,以及加上CUSTOM_MODULES,這樣modules_to_install就是全部的要安裝的模組了。
ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)
include $(BUILD_SYSTEM)/Makefile

然後把modules_to_install的值全部賦給ALL_DEFAULT_INSTALLED_MODULES,接著載入build/core/Makefile。這個Makefile會使用
ALL_DEFAULT_INSTALLED_MODULES變數最終生成所有的映象檔案。生成映象檔案的過程放在下一節討論。

四.編譯所有模組

依賴關係我們在一開始就做了簡單的梳理,現在開始分析編譯所有模組的依賴關係。

從droid目標定義的地方來看,沒有看到它的依賴,但我們向下搜尋,就會發現:

.PHONY: apps_only
apps_only: $(unbundled_build_modules)

droid: apps_only
# Building a full system-- the default is to build droidcore
droid: droidcore dist_files

我們會發現它有出現了兩個依賴,那它到底依賴哪一個呢?

droid依賴哪一個取決於ifneq ($(TARGET_BUILD_APPS),)是否成立,也就是有沒有給TARGET_BUILD_APPS賦值過,原始碼如下:

ifneq ($(TARGET_BUILD_APPS),)
  # If this build is just for apps, only build apps and not the full system by default.

  unbundled_build_modules :=
  ifneq ($(filter all,$(TARGET_BUILD_APPS)),)
    # If they used the magic goal "all" then build all apps in the source tree.
    unbundled_build_modules := $(foreach m,$(sort $(ALL_MODULES)),$(if $(filter APPS,$(ALL_MODULES.$(m).CLASS)),$(m)))
  else
    unbundled_build_modules := $(TARGET_BUILD_APPS)
  endif

...

.PHONY: apps_only
apps_only: $(unbundled_build_modules)

droid: apps_only

else # TARGET_BUILD_APPS
  $(call dist-for-goals, droidcore, \
    $(INTERNAL_UPDATE_PACKAGE_TARGET) \
    $(INTERNAL_OTA_PACKAGE_TARGET) \
    $(BUILT_OTATOOLS_PACKAGE) \
    $(SYMBOLS_ZIP) \
    $(INSTALLED_FILES_FILE) \
    $(INSTALLED_BUILD_PROP_TARGET) \
    $(BUILT_TARGET_FILES_PACKAGE) \
    $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
    $(INSTALLED_RAMDISK_TARGET) \
   )
# Building a full system-- the default is to build droidcore
droid: droidcore dist_files

endif # TARGET_BUILD_APPS

我們期望的是整個系統的編譯,所以,droid依賴的是droidcore 和 dist_files

4.1droidcore的定義:

# Build files and then package it into the rom formats
.PHONY: droidcore
droidcore: files \
	systemimage \
	$(INSTALLED_BOOTIMAGE_TARGET) \
	$(INSTALLED_RECOVERYIMAGE_TARGET) \
	$(INSTALLED_USERDATAIMAGE_TARGET) \
	$(INSTALLED_CACHEIMAGE_TARGET) \
	$(INSTALLED_VENDORIMAGE_TARGET) \
	$(INSTALLED_FILES_FILE)

可以droidcore又是一個偽目標,它又依賴於files 等一系列目標,從名字來看,這些目標應該是systemimage,userdataimage,recoryimage等,也就是說,droidcore的最終目的就是生成system.img,userdata.img等系統映象檔案。

看到變數的定義就明白了:

1.boot.img:

INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img

2.recovery.img:

INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img

3.userdata.img:

INSTALLED_USERDATAIMAGE_TARGET := $(BUILT_USERDATAIMAGE_TARGET)

  --->BUILT_USERDATAIMAGE_TARGET := $(PRODUCT_OUT)/userdata.img

4.cache.img

INSTALLED_CACHEIMAGE_TARGET := $(BUILT_CACHEIMAGE_TARGET)

 --->BUILT_CACHEIMAGE_TARGET := $(PRODUCT_OUT)/cache.img

5.vendor.img

INSTALLED_VENDORIMAGE_TARGET := $(BUILT_VENDORIMAGE_TARGET)

BUILT_VENDORIMAGE_TARGET := $(PRODUCT_OUT)/vendor.img

因此,droidcore的最終目的就是生成這些.Img檔案。

dist_files的定義:

# dist_files only for putting your library into the dist directory with a full build.
.PHONY: dist_files
從定義來看,dist_files也是個偽目標,並且它沒有任何依賴,作用是完整編譯系統的時候拷貝庫檔案。

4.2.files

它的第一個目標是files:

# All the droid stuff, in directories
.PHONY: files
files: prebuilt \
        $(modules_to_install) \
        $(INSTALLED_ANDROID_INFO_TXT_TARGET)、

1.1files又依賴了三個目標,第一個是prebuilt:

# -------------------------------------------------------------------
# This is used to to get the ordering right, you can also use these,
# but they're considered undocumented, so don't complain if their
# behavior changes.
.PHONY: prebuilt
prebuilt: $(ALL_PREBUILT)

prebuilt又是一個偽目標,它又依賴於ALL_PREBUILT變數指向的目標,ALL_PREBUILT是一些預編譯模組:

Android.mk (makefile\frameworks\base\cmds\bmgr):ALL_PREBUILT += $(TARGET_OUT)/bin/bmgr
Android.mk (makefile\frameworks\base\cmds\ime):ALL_PREBUILT += $(TARGET_OUT)/bin/ime
Android.mk (makefile\frameworks\base\cmds\input):ALL_PREBUILT += $(TARGET_OUT)/bin/input
Android.mk (makefile\frameworks\base\cmds\pm):ALL_PREBUILT += $(TARGET_OUT)/bin/pm
Android.mk (makefile\frameworks\base\cmds\svc):ALL_PREBUILT += $(TARGET_OUT)/bin/svc

4.3modules_to_install 

modules_to_install := $(sort \
    $(ALL_DEFAULT_INSTALLED_MODULES) \
    $(product_FILES) \
    $(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \
    $(CUSTOM_MODULES) \
  )

這個變數之前已經分析過,它包含所有的要安裝的模組,make會為這個目標生成依賴關係鏈,也就是會給其中的每一個模組生成依賴關係鏈,然後編譯每一個模組,這個過程在上一節中已經說過了。
至此,所有應該編譯的模組都已經被編譯,剩下的就是打包映象檔案了。這將在下一節討論。