make 是一个自动化构建工具,make 通过读取 Makefile 的文件,将源代码自动构建成可执行程序和库文件。
而 Makefile 文件中定义了目标程序的依赖关系和生成目标程序的相关规则。
在早期, make 被包含在 Unix 系统中,随着 GNU/Linux 从 Unix 衍生出来并发扬光大,GNU/Linux 保留并扩展了原始的 make ,加入了许多内置函数和自动变量等等,形成了GNU make 。
make 早期主要用于构建C语言开发的项目,后来逐渐发展,广泛用于构建 C、C++、java 等各种语言开发的项目。
Android 6.0 及以下版本的系统源码就是使用 Makefile(Android.mk) 来构建的,后来在 Android 8.0 及之后,谷歌极力推广 Android.bp,但实际上各个原厂的 Android9.0、Android10.0 系统源码代码中仍有不少的 Android.mk 文件。
Java 项目中常用的构建工具有 ant、maven、gradle,它们都有自己的命令工具、构建规则、配置文件,例如,
build.gradle同样的,make 作为自动化构建的祖师爷,也有着自己的命令工具、构建规则、配置文件。
Makefile在 Makefile 文件中,描述了工程的构建规则,由命令工具 make 来解释其中的规则。make 在执行时,需要读取至少一个 Makefile 文件。
一个完整的 Makefile 一般包含 4 个元素:
“#” 字符后的内容被作为是注释内容例如,Makefile 中的关键字有如下几种:
Makefile 中的内置函数、环境变量、自动化变量也很多,篇幅有限,这里就不一一举例了。
我们重点看下 Makefile 的在 Android 中的应用,在 Android 中的 Android.mk 就是一个 Makefile 文件。
Android.mk 是 Android 提供的一种 Makefile 文件,属于 GUN makefile 的一部分,会被编译系统解析一次或多次,因此我们应尽量少的在 Android.mk 中声明变量,也不要假定任何东西不会在解析过程中定义。
Android.mk 文件主要是告诉编译系统,以什么样的规则编译我们的源代码,并生成对应的目标文件,目标文件可以分为以下几种:
.a 文件.so 文件注意:只有动态库可以被 install 或者 copy 到 apk,静态库则可以被链接入动态库。
它是用来指定诸如编译生成 so 库名、引用的头文件目录、需要编译的 .c 或 .cpp 文件和 .a 静态库文件等。
Android.mk 文件语法允许我们将 source 打包成一个 modules,而这个 modules 可以是静态库或者动态库。我们可以在一个 Android.mk 中定义一个或多个 modules, 也可以将同一份 source 加进多个 modules。
Build System 帮我们处理了很多细节而不需要我们再关心,例如:我们不需要在 Android.mk 中列出头文件和外部依赖文件。
首先看一个最简单的 Android.mk 的例子:
# 每个 Android.mk 文件必须以定义 LOCAL_PATH 为开始,它用于在开发 tree 中查找源文件
# 宏 my-dir 则由 Build System 提供,返回包含 Android.mk 的目录路径
LOCAL_PATH := $(call my-dir)
# CLEAR_VARS 变量由 Build System 提供,并指向一个指定的 GNU Makefile,
# 它负责清理很多 LOCAL_xxx,例如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES 等等。但不清理LOCAL_PATH,
# 这个清理动作是必须的,因为所有的编译控制文件由同一个 GNU Makefile 解析和执行,其变量是全局的,所以清理后才能避免相互影响
include $(CLEAR_VARS)
# LOCAL_MODULE 模块必须定义,以表示 Android.mk 中的每个模块,名字必须唯一且不包含空格
# Build System 会自动添加适当的前缀和后缀
# 例如,foo,要产生动态库,则生成 libfoo.so,但请注意:如果模块名被定为:libfoo,则生成 libfoo.so. 不再加前缀
LOCAL_MODULE := hello
# LOCAL_SRC_FILES 变量必须包含将要打包如模块的 C/C++ 源码,我们不必列出头文件,build System 会自动帮我们找出依赖文件
# 缺省的 C++ 源码的扩展名为 .cpp,也可以通过 LOCAL_CPP_EXTENSION 修改。
LOCAL_SRC_FILES := hello.c
# BUILD_SHARED_LIBRARY 是 Build System 提供的一个变量,指向一个 GNU Makefile Script
# 它负责收集自从上次调用 include $(CLEAR_VARS) 后的所有 LOCAL_XXX 信息,并决定编译为什么
# BUILD_STATIC_LIBRARY:编译为静态库。
# BUILD_SHARED_LIBRARY:编译为动态库
# BUILD_EXECUTABLE:编译为 Native C 可执行程序
# BUILD_PREBUILT:Android 8.1 以及之后的版本使用
include $(BUILD_SHARED_LIBRARY)
这个例子中,目的是利用 Android.mk 生成 so 文件,每行代码的含义注释写得很清楚了,
另外要注意的是:在 Android 8.1 中,已经将 PREBUILT_STATIC_LIBRARY 和 PREBUILT_SHARED_LIBRARY 两个宏废弃,统一使用 BUILD_PREBUILT 预编译,并通过 LOCAL_MODULE_CLASS 来指定编译文件类型。如:
LOCAL_MODULE_CLASS := STATIC_LIBRARIES
# LOCAL_MODULE_CLASS := SHARED_LIBRARIES
# LOCAL_MODULE_CLASS := APPS
include $(BUILD_PREBUILT)
那么,如何编写 Android.mk 编译一个 apk 呢?
可以参照下面这个示例:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := Test
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED # LOCAL_CERTIFICATE := platform (使用平台签名)
# 可选项,如果不添加此变量,则预装到 system/app 下,此 apk 将不能被卸载,
# 添加后,被安装到 data/app 目录下,可卸载
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
include $(BUILD_PREBUILT)
如果 apk 还包含本地的 so 库,则应该这么写:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := Test
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED # LOCAL_CERTIFICATE := platform (使用平台签名)
# 引入本地 so 库
LOCAL_PREBUILT_JNI_LIBS := \
/lib/so1.so \
/lib/so2.so \
/lib/so3.so
LOCAL_DEX_PREOPT := true
# 可选项,如果不添加此变量,则预装到 system/app 下,此 apk 将不能被卸载,
# 添加后,被安装到 data/app 目录下,可卸载
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
include $(BUILD_PREBUILT)
我们先看看 Android 系统编译构建的发展演变过程:
GNU Make 和 Android.mkninja 和 katiAndroid.bp 来替换 Android.mk,引入 Soong 编译系统Android.bp以上涉及到的名词是什么意思呢?下面来简单介绍一下:
ninja:是一个编译框架,会根据相应的 ninja 格式的配置文件进行编译,ninja 文件一般不会手动修改,而是通过将 Android.bp 文件转换成 ninja 格文件来编译
Android.bp:它的出现是为了替换掉 Android.mk 文件,但它不支持条件语句,所以在实际项目中,如果构建的脚本必须包含条件语句,建议使用 Android.mk 或使用 Go 语言
Soong:它是为了 Android 系统编译而设计出来的工具,与 make 编译系统类似,可以认为它对标的是 make 编译系统,Soong 主要负责对 Android.bp 进行语义解析,并将其转换成 ninja 文件,Soong 还会编译生成一个 androidmk 命令,用于将 Android.mk 文件转换为 Android.bp 文件
Blueprint:它是生成、解析 Android.bp 的工具,是 Soong 的一部分,Blueprint 只是负责解析文件格式
Kati:专为 Android 开发的一个基于 Golang 和 C++ 的工具,主要功能是把 Android.mk 文件转换成 ninja 文件
Android.bp、Android.mk、ninja 之间转换关系图如下:

那么为什么要谷歌要逐渐遗弃 GNU Make 而使用 Soong 呢?
原因是使用 GNU Make 编译,在 Android 层面慢慢变得缓慢、容易出错、无法扩展且难以测试,而 Soong 构建系统正好提供了 Android 系统构建所需的灵活性。
Android.bp 的语法在设计上要比 Android.mk 简单,但是它不支持条件语句,所以在实际项目中,如果构建的脚本必须包含条件语句,建议使用 Android.mk 或使用 Go 语言。Android.bp 文件中的模块以 模块类型 开头,然后是一组键值对属性:name: value,
在 Android.bp 中我们会基于模块类型来构建我们所需要的东西,常用的有以下几种类型:
android_app
用于构建 apk,与 Android.mk 中的BUILD_PACKAGE作用相同。
java_library
用于将源码构建并链接到设备的 .jar 文件中。
默认情况下,java_library 只有一个变量,它生成一个包含根据设备引导类路径编译的 .class 文件的 .jar 包。生成的 jar 不适合直接安装在设备上,通过会用作另一个模块的 static_libs 依赖项。
如果指定 “installable:true” 将生成一个包含 classes.dex 文件的 .jar 文件,适合在设备上安装。指定 'host_supported:true' 将产生两个变量,一个根据 device 的 bootclasspath 编译,另一个根据 host 的 bootclasspath 编译。
java_library_static
作用等同于 java_library,但是 java_library_static 已过时,不推荐使用
android_library
将源码与 Android 资源文件一起构建并链接到设备的 .jar 文件中。
android_library 有一个单独的变体,它生成一个包含根据 device 的 bootclasspath 编译的 .class 文件的 .jar 文件,以及一个包含使用 aapt2 编译的android资源的 .package-res.apk 文件。生成的 apk 文件不能直接安装在设备上,但可以用作 android_app 模块的 static_libs 依赖项。
cc_library
为 device 或 host 创建静态库或共享库。
默认情况下,cc_library 具有针对设备的单一变体。指定 host_supported:true 还会创建一个以主机为目标的库。
与 cc_library 相关的模块类型还有 cc_library_shared、cc_library_headers、cc_library_static等。
下面是一个简单示例:
// 表示该模块用于构建一个 apk
android_app {
// 模块都必须具有 name 属性,且值是唯一的
name: "Test",
// 以字符串列表的形式指定用于构建模块的源文件
srcs: [
"src/**/*.java",
"com/example/xxx/*.aidl",
],
// 引入静态依赖库
static_libs: [
"androidx.cardview_cardview",
"androidx.recyclerview_recyclerview",
"TestLib",
],
// 引入java库
libs: ["android.car"],
// 指定资源文件的位置
resource_dirs: ["res"],
// 设定 apk 安装路径为 priv-app
privileged: true,
// 是否启用代码优化,android_app 中默认为 true,java_library 中默认为 false
optimize: {
enabled: false,
},
// 是否预先生成 dex 文件,默认为 true。
// 该属性会影响应用的首次启动速度以及 Android 系统的启动速度
dex_preopt: {
enabled: false,
},
// 设置该标记后会使用 sdk 的 hide 的 api 來编译,
// 如果编译的 APK 中需要使用系统级 API,必须设定该值,
// 和 Android.mk 中的 LOCAL_PRIVATE_PLATFORM_APIS 的作用相同
platform_apis: true,
// 表示生成的 apk 会被安装在系统的 product 分区,
// 和 Android.mk 中 LOCAL_PRODUCT_MODULE 作用相同
product_specific: true,
// 用于指定 APK 的签名方式
certificate: "platform",
}
// 表示该模块用于构建一个 Lib
android_library {
name: "TestLib",
srcs: [
"xxx/**/*.java",
"xxx/**/*.kt",
],
resource_dirs: ["res"],
// 用于指定 Manifest 文件
manifest: "AndroidManifest-withoutActivity.xml",
platform_apis: true,
optimize: {
enabled: false,
},
dex_preopt: {
enabled: false,
},
static_libs: [
"androidx.cardview_cardview",
"androidx.recyclerview_recyclerview",
"androidx.palette_palette",
"car-assist-client-lib",
"android.car.userlib",
"androidx-constraintlayout_constraintlayout"
],
libs: ["android.car"],
}
在该例子中,在 Test 模块中,将 TestLib 模块作为静态依赖库引入了,如果执行 make Test 命令,将会生成 Test.apk 文件,
需要注意的是,certificate 用于指定APK的签名方式,而Android 中共有四中签名方式:
home/contacts 进程共享数据,则使用该签名media/download 系统中的一环,则使用该签名