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.mk
ninja
和 kati
Android.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
系统中的一环,则使用该签名