Android 编译系统--03:make编译过程

本文转载自:make编译过程-Android10.0编译系统(三)

本文基于Android 10.0源码分析

1.概述

  上一节,我们理清了编译环境初始化的过程,环境变量已经加载,并配置了编译目标,接下来执行一个make命令我们就能够进行编译。make之后是怎么完成编译的,这个很有意思,我们一起往下探讨。

2.Android系统的编译历程

  Android 7.0 Google引入了soong构建系统,用来逐步替代GNU make的编译。因此在Android 10.0上,make执行后,我们走的是soong构建环境。

  Android系统的编译历程:

编译系统3-1.PNG

3.Soong编译系统家族成员

  从下图可知,mk文件被编译成了out/build-aosp_arm.ninja和out/build-aosp_arm-package.ninja,bp文件被编译成了out/soong/build.ninja,这三个ninja文件又被合并成out/combined-aosp_arm.ninja,最终通过ninja工具来编译out/combined-aosp_arm.ninja完成最终的编译。

编译系统3-2.PNG

4.make的流程图

  soong构建的流程图如下图所示:

编译系统3-3.PNG

5.make()

  执行完make命令后,会调用envsetup.sh的make()函数进行处理。

# build/envsetup.sh
function make()
{
    _wrap_build $(get_make_command "$@") "$@"
}

从get_make_command()可以看出:make后,真正执行编译的入口是:build/soong/soong_ui.bash。

function get_make_command()
{
    # If we're in the top of an Android tree, use soong_ui.bash instead of make
    if [ -f build/soong/soong_ui.bash ]; then
        # Always use the real make if -C is passed in
        for arg in "$@"; do
            if [[ $arg == -C* ]]; then
                echo command make
                return
            fi
        done
        echo build/soong/soong_ui.bash --make-mode
    else
        echo command make
    fi
}

6.soong_ui.bash

6.1 soong_ui.bash调用栈

编译系统3-4.png

soong_ui.bash执行过程:

  1. source microfactory.bash,得到一些函数命令, 例如:soong_build_go;

  2. 编译/build/soong/cmd/soong_ui/main.go,生成 out/soong_ui这个可执行程序;

  3. 执行命令:out/soong_ui --make-mode ,执行了make命令,会把"build/make/core/main.mk" 加到构建环境中,同时启动kati、blueprint-soong、ninja的编译。

  接下来根据调用栈的流程,来详细分析编译的过程。

6.2 build/soong/soong_ui.bash

  soong_ui.bash用来配置一些资源环境,得到一些函数命令,例如:soong_build_go,最终回退到根目录,执行out/soong_ui --make-mode进行真正的构建。

# Save the current PWD for use in soong_ui
export ORIGINAL_PWD=${PWD}
export TOP=$(gettop)
source ${TOP}/build/soong/scripts/microfactory.bash

soong_build_go soong_ui android/soong/cmd/soong_ui

cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"

6.2.1 /soong/../microfactory.bash

  得到build_go的函数命令,并提供soong_build_go的函数执行方法。

# /build/soong/scripts/microfactory.bash
function soong_build_go
{
    BUILDDIR=$(getoutdir) \
      SRCDIR=${TOP} \
      BLUEPRINTDIR=${TOP}/build/blueprint \
      EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path github.com/golang/protobuf=${TOP}/external/golang-protobuf" \
      build_go $@
}

source ${TOP}/build/blueprint/microfactory/microfactory.bash

6.2.2 /blueprint/../microfactory.bash

  build_go主要目的就是用来构建生成out/soong_ui这个可执行程序,用于参与最终的编译。

# /build/blueprint/microfactory/microfactory.bash
function build_go
{
    # Increment when microfactory changes enough that it cannot rebuild itself.
    # For example, if we use a new command line argument that doesn't work on older versions.
    local mf_version=3

    local mf_src="${BLUEPRINTDIR}/microfactory"
    local mf_bin="${BUILDDIR}/microfactory_$(uname)"
    local mf_version_file="${BUILDDIR}/.microfactory_$(uname)_version"
    local built_bin="${BUILDDIR}/$1"
    local from_src=1

    if [ -f "${mf_bin}" ] && [ -f "${mf_version_file}" ]; then
        if [ "${mf_version}" -eq "$(cat "${mf_version_file}")" ]; then
            from_src=0
        fi
    fi

    local mf_cmd
    if [ $from_src -eq 1 ]; then
        # `go run` requires a single main package, so create one
        local gen_src_dir="${BUILDDIR}/.microfactory_$(uname)_intermediates/src"
        mkdir -p "${gen_src_dir}"
        sed "s/^package microfactory/package main/" "${mf_src}/microfactory.go" >"${gen_src_dir}/microfactory.go"

        mf_cmd="${GOROOT}/bin/go run ${gen_src_dir}/microfactory.go"
    else
        mf_cmd="${mf_bin}"
    fi

    # GOROOT must be absolute because `go run` changes the local directory
    GOROOT=$(cd $GOROOT; pwd) ${mf_cmd} -b "${mf_bin}" \
            -pkg-path "github.com/google/blueprint=${BLUEPRINTDIR}" \
            -trimpath "${SRCDIR}" \
            ${EXTRA_ARGS} \
            -o "${built_bin}" $2

    if [ $? -eq 0 ] && [ $from_src -eq 1 ]; then
        echo "${mf_version}" >"${mf_version_file}"
    fi
}

soong_ui最终的编译命令展开为:

$(cd /prebuilts/go/linux-x86/; pwd) /out/microfactory_Linux 
  -b "/out/microfactory_Linux" \
  -pkg-path "github.com/google/blueprint=/build/blueprint" \
  -trimpath "./" \
  -pkg-path android/soong=/build/soong 
  -pkg-path github.com/golang/protobuf=/external/golang-protobuf} \
  -o "out/soong_ui" android/soong/cmd/soong_ui

(1)从上面的流程可知,生成soong_ui经历几件事情

  • 通过/build/blueprint/microfactory/microfactory.go编译出/out/microfactory_Linux;

  • 使用/out/microfactory_Linux来编译soong_ui。

  microfactory是一个增量编译go程序的工具。它类似于“go install”,但不需要GOPATH。包->路径映射可以指定为命令行选项:

-pkg-path android/soong=build/soong
-pkg-path github.com/google/blueprint=build/blueprint

其实microfactory就是一个高级一点的go命令,它自己由go编出来,又代替了一部分go的部分功能,鸡生蛋,蛋生鸡的故事,这里得到了完美解释 。

(2)microfactory编译示例

  1. 准备go的代码

    1)在/home/ingresge/AP/AOSP_Q中创建一个目录hello;

    2)创建hello.go---vim hello/hello.go;

    3)在其中打印一个“Hello,Go!”。

package main

import (
"log"
"os"
)

func main() {
  testlog := log.New(os.Stderr, "", log.Ltime)
  testlog.Println("Hello,Go!")
}
  1. 使用microfactory编译hello.go
/home/ingresge/AP/AOSP_Q/out/microfactory_Linux -pkg-path android/hello=/home/ingresge/AP/AOSP_Q/hello -trimpath /home/ingresge/AP/AOSP_Q/hello -o /home/ingresge/AP/AOSP_Q/out/hellogo android/hello/
  1. 运行

    1)执行命令:./out/hellogo

    2)输出结果:

17:18:44 Hello,Go!

6.3 soong_ui

  soong_ui是通过编译build/soong/cmd/soong_ui/main.go得来,我们接下来分析一下main.go的一些流程。

6.3.1 main.go调用栈

编译系统3-5.png

6.3.2 main()

  soong_ui启动编译的入口。

// build/soong/cmd/soong_ui/main.go
func main() {
       var stdio terminal.StdioInterface
       stdio = terminal.StdioImpl{}

       // dumpvar uses stdout, everything else should be in stderr
       if os.Args[1] == "--dumpvar-mode" || os.Args[1] == "--dumpvars-mode" {
               stdio = terminal.NewCustomStdio(os.Stdin, os.Stderr, os.Stderr)
       }

       writer := terminal.NewWriter(stdio)
       defer writer.Finish()

       log := logger.New(writer) //构造go编译的日志系统
       defer log.Cleanup()

       if len(os.Args) < 2 || !(inList("--make-mode", os.Args) ||
               os.Args[1] == "--dumpvars-mode" ||
               os.Args[1] == "--dumpvar-mode") {

               log.Fatalln("The `soong` native UI is not yet available.")
       }

       ctx, cancel := context.WithCancel(context.Background())
       defer cancel()

       trace := tracer.New(log)
       defer trace.Close()

       met := metrics.New()

       stat := &status.Status{}
       defer stat.Finish()
       stat.AddOutput(terminal.NewStatusOutput(writer, os.Getenv("NINJA_STATUS"),
               build.OsEnvironment().IsEnvTrue("ANDROID_QUIET_BUILD")))
       stat.AddOutput(trace.StatusTracer())

       build.SetupSignals(log, cancel, func() {
               trace.Close()
               log.Cleanup()
               stat.Finish()
       })

       buildCtx := build.Context{ContextImpl: &build.ContextImpl{
               Context: ctx,
               Logger:  log,
               Metrics: met,
               Tracer:  trace,
               Writer:  writer,
               Status:  stat,
       }}
       var config build.Config
       if os.Args[1] == "--dumpvars-mode" || os.Args[1] == "--dumpvar-mode" {
               config = build.NewConfig(buildCtx) //配置环境变量
       } else {
               config = build.NewConfig(buildCtx, os.Args[1:]...)
       }

       build.SetupOutDir(buildCtx, config)

       logsDir := config.OutDir()
       if config.Dist() {
               logsDir = filepath.Join(config.DistDir(), "logs")
       }

       os.MkdirAll(logsDir, 0777)
       log.SetOutput(filepath.Join(logsDir, "soong.log"))
       trace.SetOutput(filepath.Join(logsDir, "build.trace"))
       stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
       stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))

       defer met.Dump(filepath.Join(logsDir, "build_metrics"))

       if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
               if !strings.HasSuffix(start, "N") {
                       if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
                               log.Verbosef("Took %dms to start up.",
                                       time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
                               buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano()))
                       }
               }

               if executable, err := os.Executable(); err == nil {
                       trace.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace"))
               }
       }

       // Fix up the source tree due to a repo bug where it doesn't remove
       // linkfiles that have been removed
       fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.bp")
       fixBadDanglingLink(buildCtx, "hardware/qcom/sdm710/Android.mk")
       f := build.NewSourceFinder(buildCtx, config)
       defer f.Shutdown()
       build.FindSources(buildCtx, config, f)
       if os.Args[1] == "--dumpvar-mode" {
               dumpVar(buildCtx, config, os.Args[2:])
       } else if os.Args[1] == "--dumpvars-mode" {
               dumpVars(buildCtx, config, os.Args[2:])
       } else {
               if config.IsVerbose() {
                       writer.Print("! The argument `showcommands` is no longer supported.")
                       writer.Print("! Instead, the verbose log is always written to a compressed file in the output dir:")
                       writer.Print("!")
                       writer.Print(fmt.Sprintf("!   gzip -cd %s/verbose.log.gz | less -R", logsDir))
                       writer.Print("!")
                       writer.Print("! Older versions are saved in verbose.log.#.gz files")
                       writer.Print("")
                       time.Sleep(5 * time.Second)
               }
               toBuild := build.BuildAll
               if config.Checkbuild() {
                       toBuild |= build.RunBuildTests
               }
               build.Build(buildCtx, config, toBuild) //执行构建
       }
}

主要执行soong/ui/build/build.go,从build.go就可以看到执行soong的大体流程。

  main.go中配置的toBuild为BuildProductConfig | BuildSoong | BuildKati | BuildNinja,支持productconfig\soong\kati\ninja的构建。

6.3.3 Build调用栈

编译系统3-6.png

(1)编译步骤如下:

  1. runMakeProductConfig主要配置编译参数;

  2. runSoong对工具进行编译,编译出blueprint等编译工具, 把*.bp编译成out/soong/build.ninja;

/.minibootstrap/build.ninja
- Run minibp to generate .bootstrap/build.ninja (Primary stage) - Run minibp to generate .minibootstrap/build.ninja.in
/.bootstrap/build.ninja
- Build any bootstrap_go_binary rules and dependencies -- usually the primary builder and any build or runtime dependencies. - Run the primary builder to generate build.ninja
  1. runKatiBuild,加载build/make/core/main.mk, 搜集所有的Android.mk文件生成ninja文件:out/build-aosp_arm.ninja;

  2. runKatiPackage,加载build/make/packaging/main.mk, 编译生成out/build-aosp_arm-package.ninja

  3. createCombinedBuildNinjaFile,将out/soong/build.ninja 、out/build-aosp_arm.ninja和out/build-aosp_arm-package.ninja, 合成为out/combined-aosp_arm.ninja;

  4. runNinja,运行Ninja命令, 解析combined-aosp_arm.ninja,执行编译过程。

(2)out/combined-aosp_arm.ninja内容展示如下:

builddir = out
pool local_pool
 depth = 42
build _kati_always_build_: phony
subninja out/build-aosp_arm.ninja
subninja out/build-aosp_arm-package.ninja
subninja out/soong/build.ninja

6.3.4 Build

  Build入口。

// build/soong/ui/build/build.go
func Build(ctx Context, config Config, what int) {
       ctx.Verboseln("Starting build with args:", config.Arguments())
       ctx.Verboseln("Environment:", config.Environment().Environ())

       if config.SkipMake() {
               ctx.Verboseln("Skipping Make/Kati as requested")
               what = what & (BuildSoong | BuildNinja)
       }

       if inList("help", config.Arguments()) {
               help(ctx, config, what)
               return
       } else if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
               clean(ctx, config, what)
               return
       }

       // Make sure that no other Soong process is running with the same output directory
       buildLock := BecomeSingletonOrFail(ctx, config)
       defer buildLock.Unlock()

       checkProblematicFiles(ctx)
       SetupOutDir(ctx, config)
       checkCaseSensitivity(ctx, config)
       ensureEmptyDirectoriesExist(ctx, config.TempDir())
       SetupPath(ctx, config)

       if config.StartGoma() {
               // Ensure start Goma compiler_proxy
               startGoma(ctx, config)
       }

       if what&BuildProductConfig != 0 {
               // Run make for product config
               runMakeProductConfig(ctx, config)
       }

       if inList("installclean", config.Arguments()) {
               installClean(ctx, config, what)
               ctx.Println("Deleted images and staging directories.")
               return
       } else if inList("dataclean", config.Arguments()) {
               dataClean(ctx, config, what)
               ctx.Println("Deleted data files.")
               return
       }

       if what&BuildSoong != 0 {
               // Run Soong
               runSoong(ctx, config)
       }

       if what&BuildKati != 0 {
               // Run ckati
               genKatiSuffix(ctx, config)
               runKatiCleanSpec(ctx, config)
               runKatiBuild(ctx, config)
               runKatiPackage(ctx, config)

               ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0777)
       } else {
               // Load last Kati Suffix if it exists
               if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
                       ctx.Verboseln("Loaded previous kati config:", string(katiSuffix))
                       config.SetKatiSuffix(string(katiSuffix))
               }
       }

       // Write combined ninja file
       createCombinedBuildNinjaFile(ctx, config)

       if what&RunBuildTests != 0 {
               testForDanglingRules(ctx, config)
       }

       if what&BuildNinja != 0 {
               if !config.SkipMake() {
                       installCleanIfNecessary(ctx, config)
               }

               // Run ninja
               runNinja(ctx, config)
       }
}

6.4 main.mk文件分析

  执行runKatiBuild时,有个重要的步骤,就是加载build/make/core/main.mk,main.mk文件是Android Build系统的主控文件。从main.mk开始,将通过include命令将其所有需要的.mk文件包含进来,最终在内存中形成一个包括所有编译脚本的集合,这个相当于一个巨大Makefile文件。Makefile文件看上去很庞大,其实主要由三种内容构成:变量定义、函数定义和目标依赖规则,此外mk文件之间的包含也很重要。

编译系统3-7.png

main.mk主要做了以下几件事情:

  1. 定义编译目标product;

  2. 加载config.mk来初始化相关变量,检测编译环境和目标环境;

  3. 清除规则,清除out目录中的dex文件;

  4. 加载build/croe/definitions.mk,定义了很多通用函数,供编译过程调用;

  5. 加载平台开发工具包 build/make/core/pdk_config.mk;

  6. 加载系统中所有的Android.mk,最终会被存放到out/.module_paths/Android.mk.list;

  7. Link类型检查,校验Link;

  8. 要为此产品生成的模块的基本列表由相应的产品定义文件指定,这些定义在"product_config.mk"中;

  9. 运行时APEX库,并进行检查校验;

  10. 将所有要安装的模块都保存在变量ALL_DEFAULT_INSTALLED_MODULES中,并且将build/core/Makefie文件加载进来。build/core/Makefie文件会根据要安装的模块生成system.img、super.img、boot.img和recovery.img等镜像文件的生成规则;

  11. 定义编译的image目标;

  12. 构建文件,然后将其打包成rom格式。

6.4.1 定义编译目标product

流程1:没有KATI命令时,走run_soong_ui执行,通过soong_ui.bash --make-mode进行编译;

流程2:通过kati命令编译时。

  在Android10.0中,使用的是soong构建,因此直接走流程2,确定了最终的编译目标为droid。

# build/make/core/main.mk
ifndef KATI

host_prebuilts := linux-x86
ifeq ($(shell uname),Darwin)
host_prebuilts := darwin-x86
endif

#流程1:没有KATI命令时,走run_soong_ui执行,通过soong_ui.bash --make-mode 进行编译
.PHONY: run_soong_ui
run_soong_ui:
       +@prebuilts/build-tools/$(host_prebuilts)/bin/makeparallel --ninja build/soong/soong_ui.bash --make-mode $(MAKECMDGOALS)

.PHONY: $(MAKECMDGOALS)
$(sort $(MAKECMDGOALS)) : run_soong_ui
       @#empty

else # KATI

#流程2:通过kati命令编译时,走该流程,Android10.0中走该流程
$(info [1/1] initializing build system ...)
....

#1.定义编译目标product
#这是默认目标。它必须是第一个声明的目标。
.PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL): droid_targets

.PHONY: droid_targets
droid_targets:
...
#endif #KATI

6.4.2 加载config.mk

  加载config.mk来初始化相关变量,检测编译环境和目标环境,加载clang/config.mk,配置一些编译的环境。

include build/make/core/config.mk
...
#加载out/soong/make_vars-aosp_arm.mk
include $(SOONG_MAKEVARS_MK)

#加载clang编译的一些配置
include $(BUILD_SYSTEM)/clang/config.mk
...

6.4.3 清除规则

  清除规则,清除out目录中的dex文件。

...
.PHONY: clean-dex-files
clean-dex-files:
       $(hide) find $(OUT_DIR) -name "*.dex" | xargs rm -f
       $(hide) for i in `find $(OUT_DIR) -name "*.jar" -o -name "*.apk"` ; do ((unzip -l $$i 2> /dev/null | \
                               grep -q "\.dex$$" && rm -f $$i) || continue ) ; done
       @echo "All dex files and archives containing dex files have been removed."
...

6.4.4 加载definitions.mk

  加载build/croe/definitions.mk,定义了很多通用函数,供编译过程调用。

...
include $(BUILD_SYSTEM)/definitions.mk

#加载dex_preopt.mk
include $(BUILD_SYSTEM)/dex_preopt.mk
endif # KATI
...

6.4.5 加载pdk_config.mk

  加载平台开发工具包。

...
include build/make/core/pdk_config.mk

#为userdebug、eng和non-REL生成启用动态链接器警告
ifneq ($(TARGET_BUILD_VARIANT),user)
  ADDITIONAL_BUILD_PROPERTIES += ro.bionic.ld.warning=1
else
#只要user版本不是最终版本,就启用它。
ifneq ($(PLATFORM_VERSION_CODENAME),REL)
  ADDITIONAL_BUILD_PROPERTIES += ro.bionic.ld.warning=1
endif
endif
...

6.4.6 加载系统所有的Android.mk

  • 加载系统中所有的Android.mk,最终会被存放到out/.module_paths/Android.mk.list;

  • 如果环境变量ONE_SHOT_MAKEFILE的值不等于空,也就是我们执行的是mm或者mmm命令,那么就表示要编译的是特定的模块;

  • 这些指定要编译的模块的Android.mk文件路径就保存在环境变量ONE_SHOT_MAKEFILE中,因此直接将这些Android,mk文件加载进来就获得相应的编译规则;

  • 如果环境变量ONE_SHOT_MAKEFILE的值等于空,且dont_bother不为true,会通过out/soong/Android-aosp_arm.mk来获得Android源代码工程下的所有Android.mk文件的路径列表,并存入到out/.module_paths/Android.mk.list 中。

# build/make/core/main.mk
...
ifneq ($(ONE_SHOT_MAKEFILE),)

#我们可能已经被带有子目录makefile的“mm” shell函数调用了。
include $(SOONG_ANDROID_MK) $(wildcard $(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)))

#帮助目标打印出安装路径
define register_module_install_path
.PHONY: GET-MODULE-INSTALL-PATH-$(1)
GET-MODULE-INSTALL-PATH-$(1):
       echo 'INSTALL-PATH: $(1) $(ALL_MODULES.$(1).INSTALLED)'
endef

SORTED_ALL_MODULES := $(sort $(ALL_MODULES))
UNIQUE_ALL_MODULES :=
$(foreach m,$(SORTED_ALL_MODULES),\
    $(if $(call streq,$(m),$(lastword $(UNIQUE_ALL_MODULES))),,\
        $(eval UNIQUE_ALL_MODULES += $(m))))
SORTED_ALL_MODULES :=

$(foreach mod,$(UNIQUE_ALL_MODULES),$(if $(ALL_MODULES.$(mod).INSTALLED),\
    $(eval $(call register_module_install_path,$(mod)))\
    $(foreach path,$(ALL_MODULES.$(mod).PATH),\
        $(eval my_path_prefix := GET-INSTALL-PATH-IN)\
        $(foreach component,$(subst /,$(space),$(path)),\
            $(eval my_path_prefix := $$(my_path_prefix)-$$(component))\
            $(eval .PHONY: $$(my_path_prefix))\
            $(eval $$(my_path_prefix): GET-MODULE-INSTALL-PATH-$(mod))))))
UNIQUE_ALL_MODULES :=

else # ONE_SHOT_MAKEFILE

ifneq ($(dont_bother),true)
FULL_BUILD := true

#包括系统中的所有makefile :out/.module_paths/Android.mk.list
subdir_makefiles := $(SOONG_ANDROID_MK) $(file <$(OUT_DIR)/.module_paths/Android.mk.list)
subdir_makefiles_total := $(words int $(subdir_makefiles) post finish)
.KATI_READONLY := subdir_makefiles_total

$(foreach mk,$(subdir_makefiles),$(info [$(call inc_and_print,subdir_makefiles_inc)/$(subdir_makefiles_total)] including $(mk) ...)$(eval include $(mk)))

ifneq (,$(PDK_FUSION_PLATFORM_ZIP)$(PDK_FUSION_PLATFORM_DIR))

# 加载pdk_fusion_modules.mk
include $(BUILD_SYSTEM)/pdk_fusion_modules.mk
endif # PDK_FUSION_PLATFORM_ZIP || PDK_FUSION_PLATFORM_DIR

droid_targets : blueprint_tools

endif # dont_bother

endif # ONE_SHOT_MAKEFILE
...

6.4.7 Link检查

  编译时的Link类型检查,校验Link。

...

#Link 类型检查
#“ALL_LINK_TYPES”包含所有链接类型前缀的列表(通常每个模块一个,但是apk可以“链接”到java和本机代码)。
#链接类型前缀由intermediates dir所需的所有信息组成:
#
#  LINK_TYPE:TARGET:_:2ND:STATIC_LIBRARIES:libfoo
#
#所有未在“允许”或“警告”中列出的依赖关系链接类型都将成为错误
link_type_error :=

define link-type-prefix-base
$(word 2,$(subst :,$(space),$(1)))
endef
define link-type-prefix
$(if $(filter AUX-%,$(link-type-prefix-base)),$(patsubst AUX-%,AUX,$(link-type-prefix-base)),$(link-type-prefix-base))
endef
define link-type-aux-variant
$(if $(filter AUX-%,$(link-type-prefix-base)),$(patsubst AUX-%,%,$(link-type-prefix-base)))
endef
define link-type-common
$(patsubst _,,$(word 3,$(subst :,$(space),$(1))))
endef
define link-type-2ndarchprefix
$(patsubst _,,$(word 4,$(subst :,$(space),$(1))))
endef
define link-type-class
$(word 5,$(subst :,$(space),$(1)))
endef
define link-type-name
$(word 6,$(subst :,$(space),$(1)))
endef
define link-type-os
$(strip $(eval _p := $(link-type-prefix))\
  $(if $(filter HOST HOST_CROSS,$(_p)),\
    $($(_p)_OS),\
    $(if $(filter AUX,$(_p)),AUX,android)))
endef
define link-type-arch
$($(link-type-prefix)_$(link-type-2ndarchprefix)ARCH)
endef
define link-type-name-variant
$(link-type-name) ($(link-type-class) $(link-type-os)-$(link-type-arch))
endef

...
# 验证$(1)是否可以链接到$(2)
# $(1)和$(2)都是上面定义的链接类型前缀
define verify-link-type
$(foreach t,$($(2).TYPE),\
  $(if $(filter-out $($(1).ALLOWED),$(t)),\
    $(if $(filter $(t),$($(1).WARN)),\
      $(call link-type-warning,$(1),$(2),$(t)),\
      $(call link-type-error,$(1),$(2),$(t)))))
endef

#验证所有分支/配置都有合理的警告/错误,并删除此重写
verify-link-type = $(eval $$(1).MISSING := true)
...

6.4.8 加载product_config.mk

  要为此产品生成的模块的基本列表由相应的产品定义文件指定,这些定义在"product_config.mk"中。

...
#列出特定产品安装的大多数文件,包括:
# - PRODUCT_PACKAGES, and their LOCAL_REQUIRED_MODULES
# - PRODUCT_COPY_FILES
# 要为此产品生成的模块的基本列表由相应的产品定义文件指定,这些定义在"product_config.mk"中
define product-installed-files
  $(eval _mk := $(strip $(1))) \
  $(eval _pif_modules := \
    $(PRODUCTS.$(_mk).PRODUCT_PACKAGES) \
    $(if $(filter eng,$(tags_to_install)),$(PRODUCTS.$(_mk).PRODUCT_PACKAGES_ENG)) \
    $(if $(filter debug,$(tags_to_install)),$(PRODUCTS.$(_mk).PRODUCT_PACKAGES_DEBUG)) \
    $(if $(filter tests,$(tags_to_install)),$(PRODUCTS.$(_mk).PRODUCT_PACKAGES_TESTS)) \
    $(if $(filter asan,$(tags_to_install)),$(PRODUCTS.$(_mk).PRODUCT_PACKAGES_DEBUG_ASAN)) \
    $(call auto-included-modules) \
  ) \
  $(eval ### Filter out the overridden packages and executables before doing expansion) \
  $(eval _pif_overrides := $(call module-overrides,$(_pif_modules))) \
  $(eval _pif_modules := $(filter-out $(_pif_overrides), $(_pif_modules))) \
  $(eval ### Resolve the :32 :64 module name) \
  $(eval _pif_modules_32 := $(patsubst %:32,%,$(filter %:32, $(_pif_modules)))) \
  $(eval _pif_modules_64 := $(patsubst %:64,%,$(filter %:64, $(_pif_modules)))) \
  $(eval _pif_modules_rest := $(filter-out %:32 %:64,$(_pif_modules))) \
  $(eval ### Note for 32-bit product, 32 and 64 will be added as their original module names.) \
  $(eval _pif_modules := $(call get-32-bit-modules-if-we-can, $(_pif_modules_32))) \
  $(eval _pif_modules += $(_pif_modules_64)) \
  $(eval ### For the rest we add both) \
  $(eval _pif_modules += $(call get-32-bit-modules, $(_pif_modules_rest))) \
  $(eval _pif_modules += $(_pif_modules_rest)) \
  $(call expand-required-modules,_pif_modules,$(_pif_modules),$(_pif_overrides)) \
  $(filter-out $(HOST_OUT_ROOT)/%,$(call module-installed-files, $(_pif_modules))) \
  $(call resolve-product-relative-paths,\
    $(foreach cf,$(PRODUCTS.$(_mk).PRODUCT_COPY_FILES),$(call word-colon,2,$(cf))))
endef
...

6.4.9 运行APEX库

  运行时APEX库,并进行检查校验。


...
APEX_MODULE_LIBS := \
  libadbconnection.so \
  libadbconnectiond.so \
  libandroidicu.so \
  libandroidio.so \
  libart-compiler.so \
  libart-dexlayout.so \
  libart-disassembler.so \
  libart.so \
  libartbase.so \
  libartbased.so \
  libartd-compiler.so \
  libartd-dexlayout.so \
  libartd.so \
  libartpalette.so \
  libc.so \
  libdexfile.so \
  libdexfile_external.so \
  libdexfiled.so \
  libdexfiled_external.so \
  libdl.so \
  libdt_fd_forward.so \
  libdt_socket.so \
  libicui18n.so \
  libicuuc.so \
  libjavacore.so \
  libjdwp.so \
  libm.so \
  libnativebridge.so \
  libnativehelper.so \
  libnativeloader.so \
  libnpt.so \
  libopenjdk.so \
  libopenjdkjvm.so \
  libopenjdkjvmd.so \
  libopenjdkjvmti.so \
  libopenjdkjvmtid.so \
  libpac.so \
  libprofile.so \
  libprofiled.so \
  libsigchain.so \

# Conscrypt APEX libraries
APEX_MODULE_LIBS += \
  libjavacrypto.so \

...
#如果下面的检查失败,那么某些库已经在system/lib或system/lib64中结束,而这些库只打算放入一些APEX包中。
#可能的原因是/system中的库或二进制文件已经增长了一个直接或间接拉入禁止的库的依赖关系。
#要解决此问题,请查找库所属的APEX包-在“APEX”构建模块中的“native_shared_lib”属性中搜索它(参见art/build/APEX/安卓.bp例如)。
#然后检查APEX包中是否有应该使用的导出库,即在其“native_shared_lib”属性中列出的库,对应的“cc_library”模块具有“stubs”子句(如art/libdexfile中的libdexfile_external)/安卓.bp).
#如果您找不到适合您需要的APEX导出库,或者您认为/system中应该允许您依赖的库,那么请与包含该库的APEX包的所有者联系。
#如果在APEX中导出的库出现此错误,则APEX可能配置错误,或者生成系统中出现错误。
#请联系APEX包主和/或soong-team@,或android-building@googlegroups.com外部的。
  define check-apex-libs-absence
    $(call maybe-print-list-and-error, \
      $(filter $(foreach lib,$(APEX_MODULE_LIBS),%/$(lib)), \
        $(filter-out $(foreach dir,$(APEX_LIBS_ABSENCE_CHECK_EXCLUDE), \
                       $(TARGET_OUT)/$(if $(findstring %,$(dir)),$(dir),$(dir)/%)), \
          $(filter $(TARGET_OUT)/lib/% $(TARGET_OUT)/lib64/%,$(1)))), \
      APEX libraries found in system image (see comment for check-apex-libs-absence in \
      build/make/core/main.mk for details))
  endef

  # TODO(b/129006418): The check above catches libraries through product
  # dependencies visible to make, but as long as they have install rules in
  # /system they may still be created there through other make targets. To catch
  # that we also do a check on disk just before the system image is built.
  define check-apex-libs-absence-on-disk
    $(hide) ( \
      cd $(TARGET_OUT) && \
      findres=$$(find lib* \
        $(foreach dir,$(APEX_LIBS_ABSENCE_CHECK_EXCLUDE),-path "$(subst %,*,$(dir))" -prune -o) \
        -type f \( -false $(foreach lib,$(APEX_MODULE_LIBS),-o -name $(lib)) \) \
        -print) && \
      if [ -n "$$findres" ]; then \
        echo "APEX libraries found in system image (see comment for check-apex-libs-absence" 1>&2; \
        echo "in build/make/core/main.mk for details):" 1>&2; \
        echo "$$findres" | sort 1>&2; \
        false; \
      fi; \
    )
  endef
endif
...

6.4.10 保存所有模块

  将所有要安装的模块都保存在变量ALL_DEFAULT_INSTALLED_MODULES中,并且将build/core/Makefie文件加载进来。

... 
#build/core/Makefie文件会根据要安装的模块生成system.img、super.img、boot.img和recovery.img等镜像文件的生成规则
# TODO: Remove the 3 places in the tree that use ALL_DEFAULT_INSTALLED_MODULES
# and get rid of it from this list.
modules_to_install := $(sort \
    $(ALL_DEFAULT_INSTALLED_MODULES) \
    $(product_target_FILES) \
    $(product_host_FILES) \
    $(call get-tagged-modules,$(tags_to_install)) \
    $(CUSTOM_MODULES) \
  )
...
#build/make/core/Makefile包含了我们不想污染这个顶级Makefile的额外内容。
#它希望“ALL_DEFAULT_INSTALLED_MODULES”包含当前make期间构建的所有模块,但它还进一步扩展了“ALL_DEFAULT_INSTALLED_MODULES”。
ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)
include $(BUILD_SYSTEM)/Makefile
modules_to_install := $(sort $(ALL_DEFAULT_INSTALLED_MODULES))
ALL_DEFAULT_INSTALLED_MODULES :=

...
#这是用来获得正确的秩序,你也可以使用这些,但他们被认为是没有文件,所以不要抱怨,如果他们的行为改变。
#依赖于所有复制头的内部目标(请参见复制_标题.make). 需要首先复制头的其他目标可以依赖于此目标。
.PHONY: all_copied_headers
all_copied_headers: ;

$(ALL_C_CPP_ETC_OBJECTS): | all_copied_headers

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

6.4.11 定义编译的image目标

  定义了我们编译过程中的所有image目标。

...
.PHONY: ramdisk
ramdisk: $(INSTALLED_RAMDISK_TARGET)

.PHONY: ramdisk_debug
ramdisk_debug: $(INSTALLED_DEBUG_RAMDISK_TARGET)

.PHONY: systemtarball
systemtarball: $(INSTALLED_SYSTEMTARBALL_TARGET)

.PHONY: boottarball
boottarball: $(INSTALLED_BOOTTARBALL_TARGET)

.PHONY: userdataimage
userdataimage: $(INSTALLED_USERDATAIMAGE_TARGET)

ifneq (,$(filter userdataimage, $(MAKECMDGOALS)))
$(call dist-for-goals, userdataimage, $(BUILT_USERDATAIMAGE_TARGET))
endif

.PHONY: userdatatarball
userdatatarball: $(INSTALLED_USERDATATARBALL_TARGET)

.PHONY: cacheimage
cacheimage: $(INSTALLED_CACHEIMAGE_TARGET)

.PHONY: bptimage
bptimage: $(INSTALLED_BPTIMAGE_TARGET)

.PHONY: vendorimage
vendorimage: $(INSTALLED_VENDORIMAGE_TARGET)

.PHONY: productimage
productimage: $(INSTALLED_PRODUCTIMAGE_TARGET)

.PHONY: productservicesimage
productservicesimage: $(INSTALLED_PRODUCT_SERVICESIMAGE_TARGET)

.PHONY: odmimage
odmimage: $(INSTALLED_ODMIMAGE_TARGET)

.PHONY: systemotherimage
systemotherimage: $(INSTALLED_SYSTEMOTHERIMAGE_TARGET)

.PHONY: superimage_empty
superimage_empty: $(INSTALLED_SUPERIMAGE_EMPTY_TARGET)

.PHONY: bootimage
bootimage: $(INSTALLED_BOOTIMAGE_TARGET)

.PHONY: bootimage_debug
bootimage_debug: $(INSTALLED_DEBUG_BOOTIMAGE_TARGET)

.PHONY: vbmetaimage
vbmetaimage: $(INSTALLED_VBMETAIMAGE_TARGET)

.PHONY: auxiliary
auxiliary: $(INSTALLED_AUX_TARGETS)
...

6.4.12 构建系统,打包rom

  构建文件,然后将其打包成rom格式。

...
.PHONY: droidcore
droidcore: $(filter $(HOST_OUT_ROOT)/%,$(modules_to_install)) \
    $(INSTALLED_SYSTEMIMAGE_TARGET) \
    $(INSTALLED_RAMDISK_TARGET) \
    $(INSTALLED_BOOTIMAGE_TARGET) \
    $(INSTALLED_DEBUG_RAMDISK_TARGET) \
    $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \
    $(INSTALLED_RECOVERYIMAGE_TARGET) \
    $(INSTALLED_VBMETAIMAGE_TARGET) \
    $(INSTALLED_USERDATAIMAGE_TARGET) \
    $(INSTALLED_CACHEIMAGE_TARGET) \
    $(INSTALLED_BPTIMAGE_TARGET) \
    $(INSTALLED_VENDORIMAGE_TARGET) \
    $(INSTALLED_ODMIMAGE_TARGET) \
    $(INSTALLED_SUPERIMAGE_EMPTY_TARGET) \
    $(INSTALLED_PRODUCTIMAGE_TARGET) \
    $(INSTALLED_SYSTEMOTHERIMAGE_TARGET) \
    $(INSTALLED_FILES_FILE) \
    $(INSTALLED_FILES_JSON) \
    $(INSTALLED_FILES_FILE_VENDOR) \
    $(INSTALLED_FILES_JSON_VENDOR) \
    $(INSTALLED_FILES_FILE_ODM) \
    $(INSTALLED_FILES_JSON_ODM) \
    $(INSTALLED_FILES_FILE_PRODUCT) \
    $(INSTALLED_FILES_JSON_PRODUCT) \
    $(INSTALLED_FILES_FILE_PRODUCT_SERVICES) \
    $(INSTALLED_FILES_JSON_PRODUCT_SERVICES) \
    $(INSTALLED_FILES_FILE_SYSTEMOTHER) \
    $(INSTALLED_FILES_JSON_SYSTEMOTHER) \
    $(INSTALLED_FILES_FILE_RAMDISK) \
    $(INSTALLED_FILES_JSON_RAMDISK) \
    $(INSTALLED_FILES_FILE_DEBUG_RAMDISK) \
    $(INSTALLED_FILES_JSON_DEBUG_RAMDISK) \
    $(INSTALLED_FILES_FILE_ROOT) \
    $(INSTALLED_FILES_JSON_ROOT) \
    $(INSTALLED_FILES_FILE_RECOVERY) \
    $(INSTALLED_FILES_JSON_RECOVERY) \
    $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
    auxiliary \
    soong_docs
...

#构建一个完整的系统——默认情况下是构建droidcore
droid_targets: droidcore dist_files
...

7.总结

  至此,make的流程我们基本理清了,后面还有ninja是如何把build.ninja编译出来的,image如何打包的,我们后面继续分析。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容