Android编译机制学习

1、Makefile去哪儿了

学习了 GNU makefile 中文手册部分内容之后,觉得对 AOSP 系统的编译机制掌握了原理,只要找到根目录的 Makefile ,然后一步一步跟着流程就能够对整个源码编译了如指掌, so easy !!!But ,在最近的 Android 13源码上看,根目录下没有了 Makefile ,那这个 make 命令是怎么找到目标的呢,难道 make 还有什么隐含的默认规则没有找到吗,又去看了下 GNU Makefile 中文手册,然而并没有,指定 makefile 文件就那么几种方式,在 Android 13上都不适用。

2、一探究竟

执行编译前,都有一个初始化当前 shell 环境变量的过程,熟悉无比
source build/envsetup.sh
lunch or choosecombo
make -j8 2>&1 | tee build.log
在 build/envsetup.sh 脚本中发现了端倪所在,原来在当前 shell 下执行的 make 命令并不是直接使用的 GNU make 命令,而是在该脚本中定义的一个 shell 函数而已。。。而该 make 函数实现在不同的 AOSP 版本上有着巨大的差异:

#AOSP7.1.2_r39 build/envsetup.sh 部分源码如下:
function get_make_command()
{
    echo command make
}
#AOSP8.1.0_r81 build/envsetup.sh 部分源码如下:
#后面的AOSP版本此函数实现大同小异
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函数实现没有太多变化
function make()
{
    _wrap_build $(get_make_command) "$@"
}

在 AOSP 7 及以前的版本,make 函数仅仅是把 GNU make 命令又封装了一次而已,而在 AOSP 8 开始,make 函数判断是否存在 soong_ui.bash 文件,将其实现替换为了 soong_ui.bash 进行编译。
执行 make -j8 2>&1 | tee build.log 就相当于
执行 build/soong/soong_ui.bash --make-mode -j8 2>&1 | tee build.log

3、以 AOSP 13 的代码来进行研究

http://aospxref.com/android-13.0.0_r3
soong_ui.bash 也是一个 shell 脚本,其核心内容如下:

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,其内容如下:

# 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

该脚本主要是引入函数 soong_build_go 然后再引入另一脚本build/blueprint/microfactory/microfactory.bash,用于引入函数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行代码主要是执行 build_go 函数用于生成3个可执行文件,均是由go语言编写,这3个模块具体的编译生成流程暂未深究。。。。
1. soong_ui 用于后面执行 aosp 编译
模块位置:build/soong/cmd/soong_ui/
入口源文件:build/soong/cmd/soong_build/main.go
2. mk2rbc 通过--help查看,该命令应该是用于转换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 函数
接下来就是通过 out_sys/soong_ui --make-mode -j8 2>&1 | tee build.log 执行编译了
soong_ui 是由 build/soong/cmd/soong_build/main.go 编译生成,首先会执行其 main 函数
在该函数中判断是 --make-mode 会继续执行另一个runMake函数

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 函数会继续调用另一个build模块的Build函数,该build模块源码位置:build/soong/ui/build/build.go
build.Build 函数实现

// 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语言写的,流程看不太懂,上述代码流程是根据网络大神的流程图而来


soong.jpg

1. 首先调用 runSoong 函数,具体实现:/build/soong/ui/build/soong.go
该函数主要作用是加载所有的Android bp文件,生成 /out/soong/build.ninja 文件
2. 然后调用 runKatiBuild 函数,具体实现:build/soong/ui/build/kati.go
该函数有个非常重要的步骤,就是加载build/make/core/main.mk,该文件是Android Build 系统的主控文件。从main.mk开始,将通过include命令将其所有需要的.mk文件包含进来,最终在内存中形成一个包括所有编译脚本的集合,这个相当于一个巨大Makefile文件。Makefile文件看上去很庞大,其实主要由三种内容构成: 变量定义、函数定义和目标依赖规则,此外mk文件之间的包含也很重要。所有的mk文件生成一个out/build-aosp_arm.ninja 文件。
3.然后调用runKatiPackage函数,具体实现:build/soong/ui/build/kati.go
主要作用是生成out/build-aosp_arm-package.ninja
4.然后调用createCombinedBuildNinjaFile函数,具体实现:/build/soong/ui/build/soong.go
主要作用是将上述3个步骤生成的ninja文件包含到一个最终的out/combined-aosp_arm.ninja 文件
5.执行 runNinja 函数,具体实现:build/soong/ui/build/ninja.go
Android 10 是 runNinja 函数,在 Android 13 该函数改名为 runNinjaForBuild,该函数开始使用 ninja 进行完整的编译过程。。

相关总结:
Blueprint:Blueprint是生成、解析Android.bp的工具,是Soong的一部分。Soong负责Android编译而设计的工具,而Blueprint只是解析文件格式,Soong解析内容的具体含义。Blueprint和Soong都是由Golang写的项目,从Android 7.0,prebuilts/go/目录下新增Golang所需的运行环境,在编译时使用。而在Android 6.0及以前的版本中 prebuilds 目录下没有 go 目录。

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

推荐阅读更多精彩内容