Android編譯機(jī)制學(xué)習(xí)

1、Makefile去哪兒了

學(xué)習(xí)了 GNU makefile 中文手冊(cè)部分內(nèi)容之后,覺(jué)得對(duì) AOSP 系統(tǒng)的編譯機(jī)制掌握了原理,只要找到根目錄的 Makefile ,然后一步一步跟著流程就能夠?qū)φ麄€(gè)源碼編譯了如指掌, so easy ?。。ut ,在最近的 Android 13源碼上看,根目錄下沒(méi)有了 Makefile ,那這個(gè) make 命令是怎么找到目標(biāo)的呢,難道 make 還有什么隱含的默認(rèn)規(guī)則沒(méi)有找到嗎,又去看了下 GNU Makefile 中文手冊(cè),然而并沒(méi)有,指定 makefile 文件就那么幾種方式,在 Android 13上都不適用。

2、一探究竟

執(zhí)行編譯前,都有一個(gè)初始化當(dāng)前 shell 環(huán)境變量的過(guò)程,熟悉無(wú)比
source build/envsetup.sh
lunch or choosecombo
make -j8 2>&1 | tee build.log
在 build/envsetup.sh 腳本中發(fā)現(xiàn)了端倪所在,原來(lái)在當(dāng)前 shell 下執(zhí)行的 make 命令并不是直接使用的 GNU make 命令,而是在該腳本中定義的一個(gè) shell 函數(shù)而已。。。而該 make 函數(shù)實(shí)現(xiàn)在不同的 AOSP 版本上有著巨大的差異:

#AOSP7.1.2_r39 build/envsetup.sh 部分源碼如下:
function get_make_command()
{
    echo command make
}
#AOSP8.1.0_r81 build/envsetup.sh 部分源碼如下:
#后面的AOSP版本此函數(shù)實(shí)現(xiàn)大同小異
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
       echo build/soong/soong_ui.bash --make-mode
   else
       echo command make
   fi
}
#make函數(shù)實(shí)現(xiàn)沒(méi)有太多變化
function make()
{
    _wrap_build $(get_make_command) "$@"
}

在 AOSP 7 及以前的版本,make 函數(shù)僅僅是把 GNU make 命令又封裝了一次而已,而在 AOSP 8 開(kāi)始,make 函數(shù)判斷是否存在 soong_ui.bash 文件,將其實(shí)現(xiàn)替換為了 soong_ui.bash 進(jìn)行編譯。
執(zhí)行 make -j8 2>&1 | tee build.log 就相當(dāng)于
執(zhí)行 build/soong/soong_ui.bash --make-mode -j8 2>&1 | tee build.log

3、以 AOSP 13 的代碼來(lái)進(jìn)行研究

http://aospxref.com/android-13.0.0_r3
soong_ui.bash 也是一個(gè) shell 腳本,其核心內(nèi)容如下:

export ORIGINAL_PWD=${PWD}
export TOP=$(gettop)
source ${TOP}/build/soong/scripts/microfactory.bash

soong_build_go soong_ui android/soong/cmd/soong_ui
soong_build_go mk2rbc android/soong/mk2rbc/cmd
soong_build_go rbcrun rbcrun/cmd

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

首先是source另一bash腳本build/soong/scripts/microfactory.bash,其內(nèi)容如下:

# Bootstrap microfactory from source if necessary and use it to build the
# requested binary.
#
# Arguments:
#  $1: name of the requested binary
#  $2: package name
function soong_build_go
{
    BUILDDIR=$(getoutdir) \
      SRCDIR=${TOP} \
      BLUEPRINTDIR=${TOP}/build/blueprint \
      EXTRA_ARGS="-pkg-path android/soong=${TOP}/build/soong -pkg-path prebuilts/bazel/common/proto=${TOP}/prebuilts/bazel/common/proto -pkg-path rbcrun=${TOP}/build/make/tools/rbcrun -pkg-path google.golang.org/protobuf=${TOP}/external/golang-protobuf -pkg-path go.starlark.net=${TOP}/external/starlark-go" \
      build_go $@
}

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

該腳本主要是引入函數(shù) soong_build_go 然后再引入另一腳本build/blueprint/microfactory/microfactory.bash,用于引入函數(shù)build_go

# Set of utility functions to build and run go code with microfactory
#
# Inputs:
#  ${GOROOT}
#  ${BUILDDIR}
#  ${BLUEPRINTDIR}
#  ${SRCDIR}

# Bootstrap microfactory from source if necessary and use it to build the
# requested binary.
#
# Arguments:
#  $1: name of the requested binary
#  $2: package name
#  ${EXTRA_ARGS}: extra arguments to pass to microfactory (-pkg-path, etc)
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"
        printf "\n//for use with go run\nfunc main() { Main() }\n" >>"${gen_src_dir}/microfactory.go"

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

    rm -f "${BUILDDIR}/.$1.trace"
    # 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_build_go soong_ui android/soong/cmd/soong_ui
soong_build_go mk2rbc android/soong/mk2rbc/cmd
soong_build_go rbcrun rbcrun/cmd
這3行代碼主要是執(zhí)行 build_go 函數(shù)用于生成3個(gè)可執(zhí)行文件,均是由go語(yǔ)言編寫(xiě),這3個(gè)模塊具體的編譯生成流程暫未深究。。。。
1. soong_ui 用于后面執(zhí)行 aosp 編譯
模塊位置:build/soong/cmd/soong_ui/
入口源文件:build/soong/cmd/soong_build/main.go
2. mk2rbc 通過(guò)--help查看,該命令應(yīng)該是用于轉(zhuǎn)換makefile的
模塊位置:build/soong/mk2rbc/
入口源文件:build/soong/mk2rbc/cmd/mk2rbc.go
3. rbcrun 該命令不太清楚作用
模塊位置:build/make/tools/rbcrun/
入口源文件:build/make/tools/rbcrun/cmd/rbcrun.go

備注:Go程序入口均為 main 函數(shù)
接下來(lái)就是通過(guò) out_sys/soong_ui --make-mode -j8 2>&1 | tee build.log 執(zhí)行編譯了
soong_ui 是由 build/soong/cmd/soong_build/main.go 編譯生成,首先會(huì)執(zhí)行其 main 函數(shù)
在該函數(shù)中判斷是 --make-mode 會(huì)繼續(xù)執(zhí)行另一個(gè)runMake函數(shù)

import (
    ......
    "android/soong/ui/build"
    ......
)
// list of supported commands (flags) supported by soong ui
var commands = []command{
    {
        flag:        "--make-mode",
        description: "build the modules by the target name (i.e. soong_docs)",
        config:      build.NewConfig,
        stdio:       stdio,
        run:         runMake,
    },
    ...
}
func runMake(ctx build.Context, config build.Config, _ []string) {
    ......
    build.Build(ctx, config)
}

而 runMake 函數(shù)會(huì)繼續(xù)調(diào)用另一個(gè)build模塊的Build函數(shù),該build模塊源碼位置:build/soong/ui/build/build.go
build.Build 函數(shù)實(shí)現(xiàn)

// Build the tree. Various flags in `config` govern which components of
// the build to run.
func Build(ctx Context, config Config) {
    ......
    if what&RunKati != 0 {
        genKatiSuffix(ctx, config)
        runKatiCleanSpec(ctx, config)
        runKatiBuild(ctx, config)
        runKatiPackage(ctx, config)

        ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
    }
    ......
    createCombinedBuildNinjaFile(ctx, config)
    ......
    if what&RunNinja != 0 {
        ......
        runNinjaForBuild(ctx, config)
    }
}

Go語(yǔ)言寫(xiě)的,流程看不太懂,上述代碼流程是根據(jù)網(wǎng)絡(luò)大神的流程圖而來(lái)


soong.jpg

1. 首先調(diào)用 runSoong 函數(shù),具體實(shí)現(xiàn):/build/soong/ui/build/soong.go
該函數(shù)主要作用是加載所有的Android bp文件,生成 /out/soong/build.ninja 文件
2. 然后調(diào)用 runKatiBuild 函數(shù),具體實(shí)現(xiàn):build/soong/ui/build/kati.go
該函數(shù)有個(gè)非常重要的步驟,就是加載build/make/core/main.mk,該文件是Android Build 系統(tǒng)的主控文件。從main.mk開(kāi)始,將通過(guò)include命令將其所有需要的.mk文件包含進(jìn)來(lái),最終在內(nèi)存中形成一個(gè)包括所有編譯腳本的集合,這個(gè)相當(dāng)于一個(gè)巨大Makefile文件。Makefile文件看上去很龐大,其實(shí)主要由三種內(nèi)容構(gòu)成: 變量定義、函數(shù)定義和目標(biāo)依賴規(guī)則,此外mk文件之間的包含也很重要。所有的mk文件生成一個(gè)out/build-aosp_arm.ninja 文件。
3.然后調(diào)用runKatiPackage函數(shù),具體實(shí)現(xiàn):build/soong/ui/build/kati.go
主要作用是生成out/build-aosp_arm-package.ninja
4.然后調(diào)用createCombinedBuildNinjaFile函數(shù),具體實(shí)現(xiàn):/build/soong/ui/build/soong.go
主要作用是將上述3個(gè)步驟生成的ninja文件包含到一個(gè)最終的out/combined-aosp_arm.ninja 文件
5.執(zhí)行 runNinja 函數(shù),具體實(shí)現(xiàn):build/soong/ui/build/ninja.go
Android 10 是 runNinja 函數(shù),在 Android 13 該函數(shù)改名為 runNinjaForBuild,該函數(shù)開(kāi)始使用 ninja 進(jìn)行完整的編譯過(guò)程。。

相關(guān)總結(jié):
Blueprint:Blueprint是生成、解析Android.bp的工具,是Soong的一部分。Soong負(fù)責(zé)Android編譯而設(shè)計(jì)的工具,而B(niǎo)lueprint只是解析文件格式,Soong解析內(nèi)容的具體含義。Blueprint和Soong都是由Golang寫(xiě)的項(xiàng)目,從Android 7.0,prebuilts/go/目錄下新增Golang所需的運(yùn)行環(huán)境,在編譯時(shí)使用。而在Android 6.0及以前的版本中 prebuilds 目錄下沒(méi)有 go 目錄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容