目录

一、编译流程

1、初始化编译环境

2、选择编译平台

3、开始编译

二、Soong工具

1、Soong工作原理

2、转换关系

三、make流程

1、编译开端main.mk

2、编译配置config.mk

3、配置产品product.mk

4、加载产品product_config.mk


众所周知,Android系统其实就是一个运行在Linux系统上面的应用桌面程序,当然这样概括可能不是很准确,但是他们的编译确实异曲同工之妙。

在Linux系统中,我们可以通过make命令来编译代码。执行Make命令默认会在当前目录找到一个Makefile文件,然后根据Makefile文件中的指令来对代码进行编译(makefile语法课参考《GNU make中文手册》)。也就是说make命令执行的是Makefile文件中的指令,Makefile文件中的指令可以是编译命令(例如gcc,也可以是其它命令)。

在Android系统中,随着源代码越来越复杂,光一个makefile在性能上已经满足不了,因此在该机制之上新增了自己的android.mk和android.bp方式,为了优雅的与makefile兼容,soong就此诞生了。

想看Android源码,可以到阅读网站,请点击我。

一、编译流程

google已经给出了android的原生编译流程:source build/envsetup.sh加载命令初始化环境、lunch选择平台、make执行编译命令。

1、初始化编译环境

进入android源代码根目录执行命令source build/envsetup.sh即可初始化编译环境。如下日志:

其实该过程从source命令就可以看出主要是执行了envsetup.sh脚本且加载环境变量到当前终端。脚本envsetup.sh中其实定义了很多函数,如下:

//android/build/envsetup.sh
function hmm() {
cat <<EOF
Run "m help" for help with the build system itself.
Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch:      lunch <product_name>-<build_variant>Selects <product_name> as the product to build, and <build_variant> as the variant tobuild, and stores those selections in the environment to be read by subsequentinvocations of 'm' etc.
- tapas:      tapas [<App1> <App2> ...] [arm|x86|mips|arm64|x86_64|mips64] [eng|userdebug|user]
- croot:      Changes directory to the top of the tree, or a subdirectory thereof.
- m:          Makes from the top of the tree.
- mm:         Builds and installs all of the modules in the current directory, and theirdependencies.
- mmm:        Builds and installs all of the modules in the supplied directories, and theirdependencies.To limit the modules being built use the syntax: mmm dir/:target1,target2.
- mma:        Same as 'mm'
- mmma:       Same as 'mmm'
- provision:  Flash device with all required partitions. Options will be passed on to fastboot.
- cgrep:      Greps on all local C/C++ files.
- ggrep:      Greps on all local Gradle files.
- gogrep:     Greps on all local Go files.
- jgrep:      Greps on all local Java files.
- resgrep:    Greps on all local res/*.xml files.
- mangrep:    Greps on all local AndroidManifest.xml files.
- mgrep:      Greps on all local Makefiles and *.bp files.
- owngrep:    Greps on all local OWNERS files.
- sepgrep:    Greps on all local sepolicy files.
- sgrep:      Greps on all local source files.
- godir:      Go to the directory containing a file.
- allmod:     List all modules.
- gomod:      Go to the directory containing a module.
- pathmod:    Get the directory containing a module.
- refreshmod: Refresh list of modules for allmod/gomod.
Environment options:
- SANITIZE_HOST: Set to 'address' to use ASAN for all host modules.
- ANDROID_QUIET_BUILD: set to 'true' to display only the essential messages.
Look at the source to view more functions. The complete list is:
EOFlocal T=$(gettop)local A=""local ifor i in `cat $T/build/envsetup.sh | sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; doA="$A $i"doneecho $A
}
# Get the exact value of a build variable.
function get_build_var() { }
# check to see if the supplied product is one we can build
function check_product() { }
# 设置环境变量
function setpaths() { }
function chooseproduct() { }
function add_lunch_combo() { }
function print_lunch_menu() { }
function lunch() { }
function croot() { }
function m() { }
function mm() { }
function make() { }
# 加载执行产商定义vendorsetup.sh
function source_vendorsetup() {unset VENDOR_PYTHONPATHallowed=for f in $(find -L device vendor product -maxdepth 4 -name 'allowed-vendorsetup_sh-files' 2>/dev/null | sort); doif [ -n "$allowed" ]; thenecho "More than one 'allowed_vendorsetup_sh-files' file found, not including any vendorsetup.sh files:"echo "  $allowed"echo "  $f"returnfiallowed="$f"doneallowed_files=[ -n "$allowed" ] && allowed_files=$(cat "$allowed")for dir in device vendor product; dofor f in $(test -d $dir && \find -L $dir -maxdepth 4 -name 'vendorsetup.sh' 2>/dev/null | sort); doif [[ -z "$allowed" || "$allowed_files" =~ $f ]]; thenecho "including $f"; . "$f"elseecho "ignoring $f, not in $allowed"fidonedone
}
validate_current_shell
source_vendorsetup  # 加载执行vendor分区的vendorsetup.sh  芯片产商通常会将自己的一些配置加载改文件
addcompletions

source build/envsetup.sh执行之后,为当前终端加载了上面的这些函数,之后可以在该终端执行这些命令(函数名)来执行这些函数的内容,最后调用函数source_vendorsetup加载其他目录(device vendor product)下的vendorsetup.sh脚本。注意,该命令只在当前终端生效,如果未生效那么lunch和mm等命令是无法使用的

2、选择编译平台

在Android根目录下执行lunch命令来选择当前编译平台,该命令可以直接跟上参数,也可以让其显示菜单让我们选择,如下:

lunch函数代码如下:

//android/build/envsetup.sh
function lunch()
{local answer#步骤1:判断是否带有参数$1表示第一个参数if [ "$1" ] ; thenanswer=$1else#打印当前所有productprint_lunch_menuecho -n "Which would you like? [aosp_arm-eng] "#阻塞读取用户输入的编号read answerfi#步骤2:校验用户输入的编号或者productif [ -z "$answer" ]then#answer为空默认aosp_arm-eng平台selection=aosp_arm-engelif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")then#answer是数字编号,通过get_build_var获取所有的product到choices并根据编号找到对应的product字符串local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))if [ $answer -le ${#choices[@]} ]then# array in zsh starts from 1 instead of 0.if [ -n "$ZSH_VERSION" ]thenselection=${choices[$(($answer))]}elseselection=${choices[$(($answer-1))]}fifielse#answer是字符串,即直接跟参数的情况 例如lunch full_v755-userselection=$answerfi#步骤3:切割selection,得到产品名称等这些信息保存在product version等变量中local product variant_and_version variant versionproduct=${selection%%-*} # Trim everything after first dashvariant_and_version=${selection#*-} # Trim everything up to first dashif [ "$variant_and_version" != "$selection" ]; thenvariant=${variant_and_version%%-*}if [ "$variant" != "$variant_and_version" ]; thenversion=${variant_and_version#*-}fifi#步骤4:将上面得到的product variant version 等信息赋值到全局变量中TARGET_PRODUCT=$product \TARGET_BUILD_VARIANT=$variant \TARGET_PLATFORM_VERSION=$version \build_build_var_cacheexport TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)if [ -n "$version" ]; thenexport TARGET_PLATFORM_VERSION=$(get_build_var TARGET_PLATFORM_VERSION)elseunset TARGET_PLATFORM_VERSIONfiexport TARGET_BUILD_TYPE=release#步骤5:将上面的全局变量TARGET_PRODUCT TARGET_BUILD_VARIANT等设置到环境变量中set_stuff_for_environment#步骤6:打印最新的配置printconfig#步骤7:销毁编译相关的局部变量destroy_build_var_cache
}

这里有个很有意思的问题,就是当前平台定义的product是怎么获取出来的呢?我们可以详细看看函数print_lunch_menu代码是怎么实现的,如下代码,该函数调用get_build_var,并传递参数COMMON_LUNCH_CHOICES来获取当前所有平台,COMMON_LUNCH_CHOICES被定义在AndroidProducts.mk中,后文继续介绍。

3、开始编译

最后就可以执行make或者mm等命令进行编译了,但他们的流程可能有些不一样。因为按照linux的makefile机制,make命令其实直接执行了当前目录下的makefile脚本,但是linux并没有mm或者mma等这些命令,根据下面的代码可以知道android 7.0之后采用了独有的soong编译系统直接执行了当前目录下的android.mk或者android.bp脚本。(注意android.mk一直存在,android编译系统对makefile做的一层封装,详情参考请点击我)

  • mm命令
//android/build/envsetup.sh
function _trigger_build()
(local -r bc="$1"; shift#通过soong_ui.bash进行编译if T="$(gettop)"; then_wrap_build "$T/build/soong/soong_ui.bash" --build-mode --${bc} --dir="$(pwd)" "$@"elseecho "Couldn't locate the top of the tree. Try setting TOP."fi
)
function m()
(_trigger_build "all-modules" "$@"
)
function mm()
(_trigger_build "modules-in-a-dir-no-deps" "$@"
)
function mmm()
(_trigger_build "modules-in-dirs-no-deps" "$@"
)
function mma()
(_trigger_build "modules-in-a-dir" "$@"
)
function mmma()
(_trigger_build "modules-in-dirs" "$@"
)
  • make命令
function make()
{#调用了get_make_command,参数透传过去_wrap_build $(get_make_command "$@") "$@"
}
function get_make_command()
{# If we're in the top of an Android tree, use soong_ui.bash instead of make#如果有soong_ui.bash就使用soong进行编译(android 7之后基本上采用该方式)if [ -f build/soong/soong_ui.bash ]; then# Always use the real make if -C is passed infor arg in "$@"; doif [[ $arg == -C* ]]; thenecho command makereturnfidoneecho build/soong/soong_ui.bash --make-modeelse#如果没有soong_ui.bash就使用makefile方式进行编译(android 7之前采用该方式)echo command makefi
}

二、Soong工具

随着android工程越来越大,包含的module越来越多,以makefile(包括android.mk)组织的项目编译花费的时间越来越多。谷歌在7.0开始引入了ninja进行编译系统的组织,相对于make来说ninja在大的项目管理中速度和并行方面有突出的优势,因此谷歌采用了ninja来取代之前使用的make。但是现有的android项目还是由makefile组织,因此谷歌引入了kati(后来被称为soong)将makefile(包括android.mk)翻译成ninja文件。

Android 7.0开始逐步引入kati soong(未正式使用默认关闭,需要USE_SOONG=true手动开启),将makefile文件和Android.mk文件转化成ninja文件,使用ninja文件对编译系统进行管理。

Android 8.0开始引入了Android.bp文件来替代之前的Android.mk文件。Android.bp只是纯粹的配置文件(类似json),不包括分支、循环等流程控制(如果想要进行分支循环控制可自己写go来完成),因此Android.bp文件被转化成ninja文件的效率远远高于Android.mk文件。

Android 9.0开始强制使用Android.bp来代替Android.mk。

参考:Android中的Android.bp、Blueprint 和Soong简介

参考:Android.bp语法

1、Soong工作原理

Soong源代码路径位于/android/build/soong/,从第一章的内容了解到目前的Android代码编译命令make(包括mm等命令)都基本上使用的该目录下的soong_ui.bash来进行代码编译。主要涉及到如下流程:

  • Android.mk转换成ninja文件:soong_ui.bash将指定目录下的所有Android.mk(包括makefile)文件转换成out/build-<product_name>.ninja文件
  • Android.bp转换成ninja文件:soong_ui.bash将指定目录下所有的Android.bp文件也转换成out/soong/build.ninja文件
  • 组合nijia文件:soong_ui.bash还会生成一个较小的out/combined-<product_name>.ninja文件,负责把二者组合起来作为执行入口
  • Soong根据nijia来控制源码编译

2、转换关系

通过Kati工具将Android.mk和makefile文件转换成ninja格式的文件

通过Buleprint工具解析Android.bp文件,在通过Soong将被解析后的Android.bp转换成ninja格式的文件

通过androidmk工具可以直接将没有分支和循环流程控制的Android.mk文件转换成Android.bp文件(一般开发者可能会用到该工具)

相关术语:

  • Ninja:ninja是一个编译框架,会根据相应的ninja格式的配置文件进行编译,但是ninja文件一般不会手动修改,而是通过将Android.bp文件转换成ninja格文件来编译
  • Android.mk:Android.mk其本质是执行envsetup.sh之后,编译系统对makefile进行了一层封装,让开发者更加简单的使用makefile。因此可直接把他当成makefile看待
  • Kati:kati是专为Android开发的一个基于Golang和C++的工具,主要功能是把Android中的Android.mk文件转换成Ninja文件
  • Android.bp:Android.bp的出现就是为了替换Android.mk文件。bp跟mk文件不同,它是纯粹的配置,没有分支、循环等流程控制,不能做算数逻辑运算。如果需要控制逻辑,那么只能通过Go语言编写
  • Soong:Soong类似于之前的Makefile编译系统的核心,负责提供Android.bp语义解析,并将之转换成Ninja文件。Soong还会编译生成一个androidmk命令,用于将Android.mk文件转换为Android.bp文件,不过这个转换功能仅限于没有分支、循环等流程控制的Android.mk才有效
  • Blueprint:Blueprint是生成、解析Android.bp的工具,是Soong的一部分。Soong负责Android编译而设计的工具,而Blueprint只是解析文件格式,Soong解析内容的具体含义。Blueprint和Soong都是由Golang写的项目,从Android 7.0,prebuilts/go/目录下新增Golang所需的运行环境,在编译时使用
  • androidmk:androidmk是Soong提供的一套可直接通过命令的方式将一个android.mk生成一个对应的android.bp。通常开发者需要手动替换Android.mk的时候用到

三、make流程

在android源码根目录输入make命令之后,将以树结构的方式编译整个工程所有子目录,这里只介绍一下从make是如何走到我们lunch选择的平台mk。

1、编译开端main.mk

根据前面两章的内容我们应该很容易知道,在进行模块编译的时候执行mm其实就是执行了当前目录下的android.mk或者android.bp文件里面的指定的内容。那么在根目录下执行make其本质还是执行的根目录下面的makefile文件(跟linux一模一样)。根目录下的Android.bp内容是空,makefile直接指向了build/make/core/main.mk文件,如下图:

main.mk根据名称也知道是我们编译执行的主函数,当然其比较复杂,我们比较感兴趣大概流程如下三步:

  • 导入build/make/core/config.mk进行环境变量或重要参数的配置(比如常用的CLEAR_VARS、BUILD_PACKAGE)

  • 导入build/make/core/definitions.mk也定义了一些其他变量(比如常用的my-dir、all-subdir-makefiles、all-subdir-java-files)
  • 定义了一系列规则,规则的目标就是编译要生成的目标文件(比如单独编译某个分区可以直接使用make vendorimage、make xxximage)

2、编译配置config.mk

config.mk定义了很多变量,这些变量对应某个mk文件并有对应的逻辑功能,例如CLEAR_VARS其实就是对应执行clear_vars.mk脚本,如下代码:

config.mk除了定义一些可以使用的变量之外,还导入了其他mk文件来帮他完成一些类似任务,例如配置全局变量的时候就导入了当前目录下的envsetup.mk,如下代码:

envsetup.mk主要给一些变量设置却省值(详情请点击我),但最关心的还是加载了产品配置相关的product_config.mk,如下:

3、配置产品product.mk

关于product的相关设定,则是由build/core/product_config.mk所处理,使用 build/core/product.mk提供宏载入。根据AndroidProducts.mk的內容,product_config.mk确定product目标。

由上节我们已经知道主控main.mk最终引入到了product_config.mk来进行product相关的配置,在第107行导入了product.mk来读取当前所选择的product(即lunch的时候选择的产品),如下代码:

#http://172.16.20.244:8081/MT6762_R/xref/android/build/make/core/product.mk
#android/build/make/core/product.mk#定义函数:_find-android-products-files
#函数功能:返回所有android产品的列表
#         其中android/build/make/core/config.mk为SRC_TARGET_DIR := $(TOPDIR)build/make/target
#         因此返回的是$(TOPDIR)build/make/target/product/AndroidProducts.mk文件里面定义的所有产品
define _find-android-products-files
$(file <$(OUT_DIR)/.module_paths/AndroidProducts.mk.list) \$(SRC_TARGET_DIR)/product/AndroidProducts.mk
endef#定义函数:get-product-makefiles
#函数功能:返回所有自定义产品的列表
#         根据注释其中联合变量PRODUCT_MAKEFILES和COMMON_LUNCH_CHOICES被定义在AndroidProducts.mk
#         下面代码主要逻辑根据PRODUCT_MAKEFILES和COMMON_LUNCH_CHOICES遍历所有自定义的product及对应的mk文件
define get-product-makefiles
$(sort \$(eval _COMMON_LUNCH_CHOICES :=) \$(foreach f,$(1), \$(eval PRODUCT_MAKEFILES :=) \$(eval COMMON_LUNCH_CHOICES :=) \$(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \$(eval include $(f)) \$(call _validate-common-lunch-choices,$(COMMON_LUNCH_CHOICES),$(PRODUCT_MAKEFILES)) \$(eval _COMMON_LUNCH_CHOICES += $(COMMON_LUNCH_CHOICES)) \$(PRODUCT_MAKEFILES) \) \$(eval PRODUCT_MAKEFILES :=) \$(eval LOCAL_DIR :=) \$(eval COMMON_LUNCH_CHOICES := $(sort $(_COMMON_LUNCH_CHOICES))) \$(eval _COMMON_LUNCH_CHOICES :=) \)
endef#定义函数:get-all-product-makefiles
#函数功能:获取所有product,包括自定义的和android原生的
define get-all-product-makefiles
$(call get-product-makefiles,$(_find-android-products-files))
endef

这里我们再来看看AndroidProducts.mk中是如何配置PRODUCT_MAKEFILES和COMMON_LUNCH_CHOICES,当然AndroidProducts.mk并不止一个,一般被定义在device目录下,如果多个项目工基线的话就可以在该目录下配置多个项目。如下代码:

#android/device/厂商名/项目名/AndroidProducts.mk#指定对应的makefile文件
PRODUCT_MAKEFILES := $(LOCAL_DIR)/full_vshen.mk \$(LOCAL_DIR)/vnd_vshen.mk#包含可以选择的lunch
COMMON_LUNCH_CHOICES:=\full_vshen-eng \full_vshen-userdebug \full_vshen-user \vnd_vshen-eng \vnd_vshen-userdebug \vnd_vshen-user

4、加载产品product_config.mk

上节只介绍了lunch弹出的产品菜单是被定义在哪里的呢,现在我们再来看看已经选择了某个product,编译的是如何加载我们指定product相关的makefile文件的。继续回到product_config.mk文件中,如下代码:

根据上面代码,可以得出如下逻辑,在android目录下执行make的时候,最终会根据lunch选择的product在COMMON_LUNCH_CHOICES中去寻找对应的product,然后再加载该AndroidProducts.mk文件中PRODUCT_MAKEFILES指定的.mk文件

Android编译流程相关推荐

  1. VMware下Android编译流程

    VMware下Android编译流程 文章目录 VMware下Android编译流程 一.环境 二.流程 1.下载VMware,然后一路安装,配置Ubuntu18.04LTS的镜像,[镜像下载地址]( ...

  2. Android 编译流程解析03-手动编译Apk

    Android编译流程 通过之前两篇文章,我们已经大致了解了编译相关的Gradle,它们的编译流程如下图所示,这篇文章我们来通过手动编译的方式,来模拟Gradle 编译Android APK文件. 手 ...

  3. Android 编译过程介绍,Android.mk 和 Android.bp 分析, 在源码中编译 AndroidStudio 构建的 App

    目录 一.Android 编译 1. 编译流程 2. Soong 介绍 3. build.sh 二.Android.mk 解析 三.Android.bp 解析 1. 模块类型 2. 模块属性 四.An ...

  4. 高通Android智能平台环境搭建_编译流程分析

    高通Android智能平台环境搭建_编译流程分析 高通平台环境搭建,编译,系统引导流程分析 TOC \o \h \z \u 1. 高通平台android开发总结. 7 1.1 搭建高通平台环境开发环境 ...

  5. Cocos2d-x3.3RC0的Android编译Activity启动流程分析

    http://www.itnose.net/detail/6142692.html 本文将从引擎源码Jni分析Cocos2d-x3.3RC0的Android Activity的启动流程,下面是详细分析 ...

  6. Android编译系统分析四:实战-新增一个产品

    通过上一节"android编译系统(三)-make"的分析,初步理清楚了编译初期加载产品相关信息的流程,整个过程主要涉及三个文件:1.AndroidProducts.mk,2.具体 ...

  7. Android构建流程——篇二

    文章目录 预操作 任务列表 如何查看一个task类 Task1: checkDebugClasspath 1. input/output 2. 如何找到任务实现类 3. 核心类(AppClasspat ...

  8. Android构建流程——篇一

    Android构建流程 前言 APK 构建流程 AGP(3.2.0)任务列表总览图 参考文献 前言 大家平时开发Android项目时一般都是点击AS run按钮,这样apk会自动安装到手机上,这整个过 ...

  9. Android 编译系统分析(三)

    自Android开源以来,引起了嵌入式行业一股热潮,很多嵌入式开发者表示对Android有很强的兴趣,并下载Android源码进行编译和移植.Android源码的巨大(repo下来,大概2G)给人以A ...

最新文章

  1. python实现excel数据透视表_用python建立excel的数据透视表
  2. 用友企业互联网服务产品闪亮2016中国互联网大会
  3. 二分查找离左边元素最近的(可以等于)
  4. vue-router-2-动态路由配置
  5. bzoj 2257[Jsoi2009]瓶子和燃料 数论/裴蜀定理
  6. (转)HapMap简介
  7. VS2008文档自动生成
  8. faster rcnn源码阅读笔记2
  9. bom实现方块移动_JavaScript之方块移动
  10. 鸡啄米:C++编程入门系列之一(进制数)
  11. 一个数的0次方意义思考
  12. Unity 制作小地图
  13. 转一位计算机牛人的心得,谈计算机和数学,很实用~
  14. 动态规划(dp)总结
  15. linux strcpy函数,C语言中函数strcpy ,strncpy ,strlcpy,strcpy_s的用法
  16. Lichee(二) 在sun4i_crane平台下的编译
  17. 最大子段和(C++,DP)
  18. oracle 创建工作日表,oracle下sql创建指定年份全年日期表(区分工作日)
  19. 【数据挖掘】十大算法之SVM支持向量机分类算法
  20. win10怎么安装kali双系统 Windows下安装kali双系统安装教程 全网最详细最有效 win10安装kali linux

热门文章

  1. 获取手机屏幕宽度、高度,状态栏高度,设置状态栏沉浸式
  2. 隋唐洛阳“西宫”:上阳宫的GIS视角
  3. 数学建模常用读取excel和txt代码
  4. 圣诞老人python代码_用Python给头像加上圣诞帽或圣诞老人小徽章
  5. Python编程作业【第一周】(一)
  6. 00002-layui 右侧呼出页面,PopupLayer
  7. 编译原理:短语、直接短语和句柄
  8. 推手自曝网络红人炒作全过程2014网络红人斌少
  9. linux(凝思系统)进入单用户模式
  10. Java类成员默认访问控制权限是default