Soong 构建系统

发布于:2024-07-08 ⋅ 阅读:(52) ⋅ 点赞:(0)

背景

Soong 构建系统在Android 7.0开始引入,目的是取代Make。它利用Kati GNU Make 和Ninja构建系统组件来构建Android Soong是用Go语言写的,go环境在prebuilts/go环境下,Soong在编译时,解析bp文件,转化成Ninja文件,完成Android的选择编译,解析配置过程。 Soong相当于Makefile编译系统核心,即build/make/core。

Blueprint

Blueprint由Go语言编写,是生成、解析Android.bp的工具,是Soong的一部分。Soong则是专为Android编译而设计的工具,Blueprint只是解析文件的形式,而Soong则解释内容的含义。

KATI

kati是Google专门为了Android而开发的一个小项目,基于Golang和C++。目的是为了把Android中的Makefile,转换成Ninja文件。 在最新的Android R(11)中,Google已经移除了/build/kati目录,只保留了一个预先编译出来的可执行文件:prebuilts/build-tools/linux-x86/bin/ckati kata是go语言写的,ckatai是C++写的。kati官方文档对它的描述是:kati is an experimental GNU make clone。也就是说,kati是对等make命令的。只不过kati并不执行具体的编译工作,而是生成ninja文件。kati刚开始是使用Golang编写的,但是后来验证下来发现编译速度不行,于是改成C++编写,所以现在存在两个版本:kati、ckati。

Ninja

Ninja 是Google的一名程序员推出的注重速度的构建工具。一般在Unix/Linux上的程序通过make/makefile来构建编译,而Ninja通过将编译任务并行组织,大大提高了构建速度。 Ninja是一个致力于速度的小型编译系统(类似于Make),如果把其他编译系统比做高级语言的话,Ninja就是汇编语言。通常使用Kati或soong把makefile转换成Ninja files,然后用Ninja编译。 ninja核心是由C/C++编写的,同时有一部分辅助功能由python和shell实现。由于其开源性,所以可以利用ninja的开源代码进行各种个性化的编译定制。

Makefile

Makefile是一个文本文件,是GNU make程序在执行的时候默认读取的配置文件。其关系到了整个工程的编译规则。一个工程中的源文件按类型、功能、模块分别放在若干个目录中,makefile定义了一系列规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。 其好处在于:写好makefile之后,只需要一个“make”命令,整个工程就能完全自动编译,极大地提高了软件开发的效率。

Android 编译指令

source build/envsetup.sh //step 1.初始化编译环境
lunch xxx //step 2.选择编译目标
make -j8 //step 3.执行编译
pack -d -v //step 4.打包生成镜像

编译流程图

初始化编译环境

envsetup.sh脚本:主要是定义了make、lunch等相关函数,为Android系统的编译提供支持。

shell脚本部分

1.make 指令入口

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

2.获取构建方式(以前通过make,现在改成soong方式),通过判断soong_ui.bash文件是否存在,来决定系统构建方式。

@build/envsetup.sh
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
}

3.执行构建指令,并且打印建设时间,构建结果。

@build/make/shell_utils.sh
# Pretty print the build status and duration
function _wrap_build()
{
    if [[ "${ANDROID_QUIET_BUILD:-}" == true ]]; then
      "$@"
      return $?
    fi
    local start_time=$(date +"%s")
    "$@"
    local ret=$?
    local end_time=$(date +"%s")
    local tdiff=$(($end_time-$start_time))
    local hours=$(($tdiff / 3600 ))
    local mins=$((($tdiff % 3600) / 60))
    local secs=$(($tdiff % 60))
    local ncolors=$(tput colors 2>/dev/null)
    if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
        color_failed=$'\E'"[0;31m"
        color_success=$'\E'"[0;32m"
        color_warning=$'\E'"[0;33m"
        color_reset=$'\E'"[00m"
    else
        color_failed=""
        color_success=""
        color_reset=""
    fi

    echo
    if [ $ret -eq 0 ] ; then
        echo -n "${color_success}#### build completed successfully "
    else
        echo -n "${color_failed}#### failed to build some targets "
    fi
    if [ $hours -gt 0 ] ; then
        printf "(%02g:%02g:%02g (hh:mm:ss))" $hours $mins $secs
    elif [ $mins -gt 0 ] ; then
        printf "(%02g:%02g (mm:ss))" $mins $secs
    elif [ $secs -gt 0 ] ; then
        printf "(%s seconds)" $secs
    fi
    echo " ####${color_reset}"
    echo
    return $ret
}

可以通过命令行可以看出,当编译完成时,显示构建结果

07-02 10:54:47.079  2748  2748 I lpmake  : builder.cpp:1093 [liblp] Partition vendor_a will resize from 0 bytes to 257830912 bytes
07-02 10:54:47.079  2748  2748 I lpmake  : builder.cpp:1093 [liblp] Partition product_a will resize from 0 bytes to 1762189312 bytes
07-02 10:54:47.079  2748  2748 I lpmake  : builder.cpp:1093 [liblp] Partition vendor_dlkm_a will resize from 0 bytes to 17870848 bytes
07-02 10:54:47.079  2748  2748 I lpmake  : builder.cpp:1093 [liblp] Partition system_dlkm_a will resize from 0 bytes to 475136 bytes
Invalid sparse file format at header magic
Invalid sparse file format at header magic
Invalid sparse file format at header magic
Invalid sparse file format at header magic
Invalid sparse file format at header magic
2024-07-02 10:54:49 - build_super_image.py - INFO    : Done writing image out/target/product/xxx-xxx/super.img

#### build completed successfully (05:28 (mm:ss)) ####


#### build completed successfully (05:28 (mm:ss)) ####

执行soong_ui.bash脚本

soong_ui.bash脚本主要做了两件事: 1.根据"android/soong/cmd/soong_ui/"内容,生成soong_ui的go可执行程序,生成路径:out/soong_ui 2.执行soong_ui程序

@build/soong/soong_ui.bash
...
# 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  // 1.生成soong_ui执行程序
...
exec "$(getoutdir)/soong_ui" "$@"//2.执行soony_ui程序,启动构建

soong 入口

soong_ui是个go程序,至此进入soong构建系统的世界。

@build/soong/cmd/soong_ui/main.go
func main() {
...
    preProductConfigSetup(buildCtx, config)//创建文件遍历器
    if build.SetProductReleaseConfigMaps(buildCtx, config) {
        log.Verbose("Product release config maps found\n")
        config = freshConfig()
    }

    c.run(buildCtx, config, args)//启动构建
}
func preProductConfigSetup(buildCtx build.Context, config build.Config) {

...
    f := build.NewSourceFinder(buildCtx, config)// Create a source finder.
    defer f.Shutdown()
    build.FindSources(buildCtx, config, f)//遍历整个项目,记录所有mk,bp等文件
}

所有记录信息都在 out/.module_paths/Android.bp.list 和 out/.module_paths/Android.mk.list

soong构建系统

soong构建系统最核心的步骤。其主要通过将bp、mk文件,解析成ninja文件,再通过ninja去实现系统构建任务。

@build/soong/ui/build/build.go
func Build(ctx Context, config Config) {

    ...
    runSoong(ctx, config)//step 1.处理bp文件
    ...
    runKatiBuild(ctx, config)//step 2.处理mk文件
    ...
    createCombinedBuildNinjaFile(ctx, config)//step 3.整合ninja文件
    ...
    runNinja(ctx, config)//step 4.构建
    ...
}

1.runSoong

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

@android\build\soong\ui\build\soong.go
func runSoong(ctx Context, config Config) {
    ...
    ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")//1.生成out/soong/bootstrap.ninja
    ...
    ctx.BeginTrace(metrics.RunSoong, "environment check")//2.初始环境检查
    ...
    ctx.BeginTrace(metrics.RunSoong, "bpglob")//3.创建bpglob可执行程序
    config.PrebuiltBuildTool("ninja"), ninjaArgs...) //提前编译好ninja可执行文件
    ...
    targets := make([]string, 0, 0)
    if config.JsonModuleGraph() {
        targets = append(targets, config.ModuleGraphFile())
    }
    if config.Queryview() {
        targets = append(targets, config.QueryviewMarkerFile())
    }
    if config.SoongDocs() {
        targets = append(targets, config.SoongDocsHtml())
    }
    if config.SoongBuildInvocationNeeded() {
        // This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
        targets = append(targets, config.SoongNinjaFile())
    }
    ninja(targets...)5.生成out/soong/build.ninja
    ...
}

bootstrap表示从无到有创建Soong,该阶段会先生成bootstrap相关的工具程序:,再使用编译生成的soong_build程序,生成out/soong/build.ninja文件。用于后续参与Ninja编译构建工作,可以编译终端看出,build.a523_pro_arm64.ninja是编译目标文件

我们通过verboase.log 调试信息也可以证明

[1/1] cd "$(dirname "out/host/linux-x86/bin/soong_build")" && BUILDER="$PWD/$(basename "out/host/linux-x86/bin/soong_build")" && cd / && env -i  "$BUILDER"     --top "$TOP"     --soong_out "out/soong"     --out "out"     --soong_variables out/soong/soong.a523_pro_arm64.variables -o out/soong/build.a523_pro_arm64.ninja --globListDir a523_pro_arm64 --globFile out/soong/globs-a523_pro_arm64.ninja -l out/.module_paths/Android.bp.list --available_env out/soong/soong.environment.available --used_env out/soong/soong.environment.used.a523_pro_arm64.build Android.bp

out/soong/build.a523_pro_arm64.ninja文件罗列了项目上所有的bp模块编译规则,及其相关依赖模块、SDK、签名信息、临时文件等。 该阶段在编译时,控制台打印的log如下:

[100% 1032/1032] analyzing Android.bp files and generating ninja file at out/soong/build.a523_pro_arm64.ninja
cedarx-config: sdkVersion[35], board[saturn], platformconfig[YES], afbcMode[2], grfBuild[false]

runKatiBuild

runKatiBuild, 加载 build/make/core/main.mk, 搜集所有的Android.mk文件生成out/build-xxx.ninja文件

@build\soong\ui\build\kati.go
func runKatiBuild(ctx Context, config Config) {
    ctx.BeginTrace(metrics.RunKati, "kati build")
    ...
    args := []string{
        "--writable", config.OutDir() + "/",
        "-f", "build/make/core/main.mk",
    }
    ...
    runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})//执行ckati指令,构建mk
    ...
}

1.参考soong.log的日志,runKati函数最后会引用cKati指令,加载main.mk文件,生成ninja文件。其指令如下:

2024/07/04 15:26:08.038311 build/soong/ui/logger/logger.go:290: "ckati" executing "prebuilts/build-tools/linux-x86/bin/nsjail" [-x prebuilts/build-tools/linux-x86/bin/ckati -H android-build --cwd /home1/billyfeng/project/android15_A523 -t 0 -e --proc_rw -u nobody -g nogroup --rlimit_as soft --rlimit_core soft --rlimit_cpu soft --rlimit_fsize soft --rlimit_nofile soft -R / -B /tmp -B /home1/billyfeng/project/android15_A523 -B /home1/billyfeng/project/android15_A523/out --disable_clone_newcgroup -q -- --ninja --ninja_dir=out --ninja_suffix=-a523_pro_arm64 --no_ninja_prelude --use_ninja_phony_output --regen --ignore_optional_include=out/%.P --detect_android_echo --color_warnings --gen_all_targets --use_find_emulator --werror_find_emulator --no_builtin_rules --werror_suffix_rules --werror_real_to_phony --top_level_phony --werror_phony_looks_real --werror_writable --kati_stats --writable out/ --werror_implicit_rules -f build/make/core/main.mk --werror_overriding_commands SOONG_MAKEVARS_MK=out/soong/make_vars-a523_pro_arm64.mk SOONG_ANDROID_MK=out/soong/Android-a523_pro_arm64.mk TARGET_DEVICE_DIR=device/softwinner/saturn/a523-pro KATI_PACKAGE_MK_DIR=out/target/product/a523-pro/obj/CONFIG/kati_packaging]

2.build/make/core/main.mk是什么? 从main.mk开始,将通过include命令将其所有需要的.mk文件包含进来,最终在内存中形成一个包括所有编译脚本的集合,这个相当于一个巨大Makefile文件。

文件 说明
build/make/core/main.mk Build的主控文件,主要作用是包含其他mk,以及定义几个最重要的编译目标,同时检查编译工具的版本,例如gcc、clang、java等
build/make/core/config.mk Build的配置文件,主要是区分各个产品的配置,并将这些编译器参数引入产品配置 BoardConfig.mk,同时也配置了一些编译器的路径等
build/make/core/clang/config.mk clang编译的配置文件
build/make/core/definitions.mk 最重要的 Make 文件之一,在其中定义了大量的函数。这些函数都是 Build 系统的其他文件将用到的。例如:my-dir,all-subdir-makefiles,find-subdir-files,sign-package 等,关于这些函数的说明请参见每个函数的代码注释。
build/make/core/dex_preopt.mk 定义了dex优化相关的路径和参数
build/make/core/pdk_config.mk 编译pdk的配置文件
build/make/core/Makefile 系统最终编译完成所需要的各种目标和规则
build/make/core/envsetup.mk 包含进product_config.mk文件并且根据其内容设置编译产品所需要的环境变量,并检查合法性,指定输出路径等
build/make/core/combo/select.mk 根据当前编译器的平台选择平台相关的 Make 文件
build/make/core/ninja_config.mk 解析makefile的的列表,传给kati,配置传给ninja和kati的目标
build/make/core/soong_config.mk 配置soong的环境变量,建立go变量和mk变量的json映射关系,让go变量可以获取到mk中定义的变量值

3.在启动时,就会搜索项目中所有Android.mk文件,并且记录在并记录于out/.module_paths/Android.mk.list文件。在main.mk里面,便可以根据这个文件,将所有的内容include进来。因此,在该项目下定义的任一Android.mk都可以被引用。

@build/make/core/main.mk
#
# Include all of the makefiles in the system
#
subdir_makefiles := $(SOONG_OUT_DIR)/installs-$(TARGET_PRODUCT).mk $(SOONG_ANDROID_MK)
# Android.mk files are only used on Linux builds, Mac only supports Android.bp
ifeq ($(HOST_OS),linux)
  subdir_makefiles += $(file <$(OUT_DIR)/.module_paths/Android.mk.list)
endif
subdir_makefiles += $(SOONG_OUT_DIR)/late-$(TARGET_PRODUCT).mk
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)))//遍历所有mk文件

(4)main.mk加载完成后,最终生成out/build-xxx.ninja文件,用于后续参与Ninja编译构建工作。out/build-xxx.ninja文件罗列了项目上所有的mk模块编译规则,及其相关依赖模块、SDK、签名信息、临时文件等。 在Android15生成以下.mk文件。

./build-xxx_pro_arm64-cleanspec.ninja
./build-xxx_pro_arm64-package.ninja
./build-xxx_pro_arm64.ninja

合并 ninja文件 ---createCombinedBuildNinjaFile

为了方便统一管理,Soong将out/soong/build.ninja文件 、out/build-.ninja文件和out/build--package.ninja文件, 合成为out/combined-*.ninja文件,由该文件记录所有待执行ninja文件。

var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
builddir = {{.OutDir}}
{{if .UseRemoteBuild }}pool local_pool
 depth = {{.Parallel}}
{{end -}}
pool highmem_pool
 depth = {{.HighmemParallel}}
{{if and (not .SkipKatiNinja) .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}//追加out/build-*.ninja
subninja {{.KatiPackageNinjaFile}}//追加文件out/build-*-package.ninja
{{end -}}
subninja {{.SoongNinjaFile}}//追加文件out/soong/build-xxx.ninja
`))

func createCombinedBuildNinjaFile(ctx Context, config Config) {
    ...
    file, err := os.Create(config.CombinedNinjaFile())//创建combined-*.ninja文件
    ...
    if err := combinedBuildNinjaTemplate.Execute(file, config); //执行合并动作
    ...
}

例如,在 combined-a523_pro_arm64.ninja项目中,我们可以看到包含待执行ninja文件。

builddir = out
pool highmem_pool
 depth = 7
subninja out/build-xxx_pro_arm64.ninja
subninja out/build-xxx_pro_arm64-package.ninja
subninja out/soong/build.xxx_pro_arm64.ninja

soong编译所产生文件

文件 备注
android/out/soong.log soong模块打印内容
android/out/verbose.log 控制台编译日志
android/out/dumpvars-verbose.log lunch的log信息
android/out/.ninja_log ninja模块编译log
android/out/soong_ui go可执行程序,执行soong编译
android/out/.module_paths/ 遍历整个项目,记录所有的mk、bp等文件
android/out/soong/build.ninja 项目上所有bp模块的编译规则
android/out/build-*.ninja 项目上所有mk模块的编译规则
android/out/combined-*.ninja 项目上所有模块的编译规则组合
android/out/soong/host/linux-x86/bin/androidmk mk文件转bp文件的指令

网站公告

今日签到

点亮在社区的每一天
去签到