Android.mk是Android源码中提供的一套用于编译Android系统、子模块的基于makefile语法规则的脚本文件。
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,也可以执行操作系统的命令 --《百度百科:makefile》
虽然Android 7.0 之后Google提供了一种基于新语法结构Android.bp脚本来替代语法结构繁杂的Android.mk,但是即使是Android 10 中依然有大量模块还在使用Android.mk进行构建,预计今后一段时间内,Android.mk与Android.bp依然会同时存在于Android源码中。有关Android.bp的使用我们以后再做介绍。
构成
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := hello-jni.c
LOCAL_MODULE := hello-jni
include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH := $(call my-dir)
每个Android.mk文件必须以定义LOCAL_PATH为开始。它用于在开发tree中查找源文件。宏my-dir 则由Build System提供。返回包含Android.mk的目录路径。
include $(CLEAR_VARS)
CLEAR_VARS 变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx.
例如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES等等。但不清理LOCAL_PATH.
这个清理动作是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能避免相互影响。在描述每个模块之前,必须重新声明此变量。
LOCAL_SRC_FILES
构建系统生成模块时所用的源文件,可以是单个源文件,也可以是一个文件夹。
LOCAL_MODULE
LOCAL_MODULE 变量存储您要构建的模块的名称。请在应用的每个模块中使用一次此变量。每个模块名称必须唯一,且不含任何空格。构建系统在生成最终共享库文件时,会对您分配给 LOCAL_MODULE 的名称自动添加正确的前缀和后缀。例如,上述示例会生成名为 libhello-jni.so 的库。
注意:如果模块名称的开头已经是 lib,构建系统不会添加额外的 lib 前缀;而是按原样采用模块名称,并添加 .so 扩展名。因此,比如原来名为 libfoo.c 的源文件仍会生成名为 libfoo.so 的共享对象文件。此行为是为了支持 Android 平台源文件根据 Android.mk 文件生成的库;所有这些库的名称都以 lib 开头。
include $(BUILD_SHARED_LIBRARY)
通过include $(BUILD_SHARED_LIBRARY) 编译工具,会将上面设定的一切连接到一起BUILD_SHARED_LIBRARY 变量指向一个 GNU Makefile 脚本,该脚本会收集您自最近 include 以来在 LOCAL_XXX 变量中定义的所有信息。此脚本确定要构建的内容以及构建方式。
BUILD_SHARED_LIBRARY:是Build System提供的一个变量,指向一个GNU Makefile Script。
它负责收集自从上次调用 include $(CLEAR_VARS) 后的所有LOCAL_XXX信息。并决定编译为什么。
BUILD_PREBUILT :该模块已经预先编译
BUILD_PACKAGE(既可以编apk,也可以编资源包文件,但是需要指定LOCAL_EXPORT_PACKAGE_RESOURCES:=true)
BUILD_JAVA_LIBRARY(java共享库)
BUILD_STATIC_JAVA_LIBRARY(java静态库)
BUILD_EXECUTABLE(执行文件)
BUILD_SHARED_LIBRARY(native共享库)
BUILD_STATIC_LIBRARY(native静态库)
模块
在Android.mk中我们把以include $(CLEAR_VARS)标记开头,到include $(BUILD_XXX)标记结束,这中间描述的所有行为,称为一个模块。
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
$(call all-logtags-files-under, src)
LOCAL_MODULE := settings-logtags
## 构建一个 java 类库
include $(BUILD_STATIC_JAVA_LIBRARY)
# 构建一个apk文件
include $(CLEAR_VARS)
LOCAL_PACKAGE_NAME := Settings
#省略其它描述...
LOCAL_USE_AAPT2 := true
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx-constraintlayout_constraintlayout \
#省略其它描述...
include frameworks/base/packages/SettingsLib/common.mk
include frameworks/base/packages/SettingsLib/search/common.mk
include $(BUILD_PACKAGE)
include变量
CLEAR_VARS
用于取消位于include $(CLEAR_VARS)之前定义的所有的LOCAL_XXX变量中定义的值,但是LOCAL_PATH中定义的值不会被取消。
在描述新的模块前,需要用include包含此变量。
BUILD_JAVA_LIBRARY
用于构建java类库,该类库的代码会以.dex形式存在。
BUILD_STATIC_JAVA_LIBRARY
include $(BUILD_STATIC_JAVA_LIBRARY)用于构建java类库,该类库中代码会以class文件的形式存在.如果编译出jar是用于app的开发,应该使用该变量描述。
BUILD_PACKAGE
include $(BUILD_PACKAGE)用于构建Android 应用程序安装包。
BUILD_MULTI_PREBUILT
include $(BUILD_MULTI_PREBUILT)用于构建预制库,这些库一般可以指定在lib文件夹下。
BUILD_STATIC_LIBRARIES
include $(BUILD_STATIC_LIBRARIES)用于构建native静态库。
模块描述变量
LOCAL_PATH:用于指定当前文件路径,必须在 Android.mk 文件开头定义此变量。以下示例演示了如何定义此变量:CLEAR_VARS 所指向的脚本不会清除此变量。因此,即使 Android.mk 文件描述了多个模块,也只需定义此变量一次。
LOCAL_MODULE:此变量用于设定模块的名称。指定的名称在所有模块名称中必须唯一,并且不得包含任何空格。必须先定义该名称,然后才能添加其它的脚本(CLEAR_VARS 的脚本除外)。如下定义在配合include $(BUILD_STATIC_JAVA_LIBRARY)使用时会编译时生成一个libsettings-logtags的类库,lib是编译时系统自动加上的
LOCAL_MODULE := settings-logtags
LOCAL_MODULE_FILENAME:此变量能够替换构建系统为其生成的文件默认使用的名称。例如,如果 LOCAL_MODULE 的名称为 settings-logtags,你可以强制系统将其生成的文件命名为 settingslib。
LOCAL_MODULE := settings-logtags
LOCAL_MODULE_FILENAME := settingslib
LOCAL_SRC_FILES:此变量包含构建系统生成模块时所用的源文件列表。
LOCAL_SRC_FILES := \
$(call all-logtags-files-under, src)
LOCAL_PACKAGE_NAME:此变量用于指定编译后生成的Android APK的名字。例如,如下方式,会生成一个Settings.apk的文件。
LOCAL_PACKAGE_NAME := Settings
LOCAL_CERTIFICATE:此变量用于指定APK的签名方式。如果不指定,默认使用testkey签名。
LOCAL_CERTIFICATE := platform
Android中共有四中签名方式:
testkey:普通APK,默认使用该签名。
platform:该APK完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试,这种方式编译出来的APK所在进程的UID为system。
shared:该APK需要和home/contacts进程共享数据。
media:该APK是media/download系统中的一环。
LOCAL_PRODUCT_MODULE:为true表示将此apk安装到priv-app目录下。
LOCAL_PRODUCT_MODULE := true
LOCAL_SDK_VERSION:标记SDK 的version 状态。取值范围有四个current system_current test_current core_current 。
LOCAL_SDK_VERSION := current
LOCAL_PRIVATE_PLATFORM_APIS:设置后,会使用sdk的hide的api來编译。编译的APK中使用了系统级API,必须设定该值。
LOCAL_PRIVATE_PLATFORM_APIS := true
LOCAL_USE_AAPT2:此值用于设定是否开启AAPT2打包APK,AAPT是Android Asset Packaging Tool的缩写,AAPT2在AAPT的基础做了优化。
LOCAL_USE_AAPT2 := true
LOCAL_STATIC_ANDROID_LIBRARIES:此值用于设定依赖的静态Android库
LOCAL_STATIC_ANDROID_LIBRARIES := \
androidx-constraintlayout_constraintlayout \
androidx.slice_slice-builders \
LOCAL_JAVA_LIBRARIES:此值用于设定依赖的共享java类库。LOCAL_JAVA_LIBRARIES引用的外部Java库在编译时可以找到相关的东西,但并不打包到本模块,在runtime时需要从别的地方查找,这个别的地方就是在编译时将引用的外部Java库的模块名添加到PRODUCT_BOOT_JARS。
LOCAL_JAVA_LIBRARIES := \
telephony-common \
ims-common
LOCAL_STATIC_JAVA_LIBRARIES:此值用于设定依赖的静态java类库。LOCAL_STATIC_JAVA_LIBRARIES会把引用的外部Java库直接编译打包到本模块中,在runtime时可以直接从本模块中找到相关的jar。
LOCAL_STATIC_JAVA_LIBRARIES := \
androidx-constraintlayout_constraintlayout-solver \
androidx.lifecycle_lifecycle-runtime \
androidx.lifecycle_lifecycle-extensions \
LOCAL_PROGUARD_FLAG_FILES:此值用于设定混淆的标志。
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
参考『系统设置』的Android.mk
Android.mk的编写,我们很多时候可以参考系统中已有的Android.mk,例如下面给的就是Android R中系统设置的Android.mk。该mk的位于 packages/apps/Settings 下
Android.bp文件首先是Android系统的一种编译配置文件,是用来代替原来的Android.mk文件的。在Android7.0以前,Android都是使用make来组织各模块的编译,对应的编译配置文件就是Android.mk。在Android7.0开始,Google引入了ninja和kati来编译,为啥引入ninja?因为随着Android越来越庞大,module越来越多,编译时间也越来越久,而使用ninja在编译的并发处理上较make有很大的提升。Ninja的配置文件就是Android.bp,Android系统使用Blueprint和Soong工具来解析Android.bp转换生成ninja文件。为了兼容老的mk配置文件,Android当初也开发了Kati 工具来转换mk文件生成ninja,目前Android Q里边,还是支持Android.mk方式的。相信在将来的版本中,会彻底让mk文件废弃,同时Kati也就淘汰了,只保留bp配置方式,所以我们要提前学习bp。Blueprint和Soong工具的源码在Android/build/目录下,我们可以通过查阅相关代码来学习!
模块和属性
Android.bp描述的编译对象都是以模块为组织单位的,定义一个模块从模块的类型开始,模块有不同的类型,模块包含一些属性。
cc_library_shared { //编译成动态库,类似于Android.mk中的BUILD_SHARED_LIBRARY
name: "libbluetooth_jni", //编译出的模块的名称,类似于Android.mk中的LOCAL_MODULE
srcs: [ //源文件,类似于Android.mk中的LOCAL_SRC_FILES
"com_android_bluetooth_btservice_AdapterService.cpp",
"com_android_bluetooth_hfp.cpp",
"com_android_bluetooth_hfpclient.cpp",
"com_android_bluetooth_a2dp.cpp",
"com_android_bluetooth_a2dp_sink.cpp",
"com_android_bluetooth_avrcp.cpp",
"com_android_bluetooth_avrcp_controller.cpp",
"com_android_bluetooth_hid.cpp",
"com_android_bluetooth_hidd.cpp",
"com_android_bluetooth_hdp.cpp",
"com_android_bluetooth_pan.cpp",
"com_android_bluetooth_gatt.cpp",
"com_android_bluetooth_sdp.cpp",
],
include_dirs: [ //用户指定的头文件查找路径,类似于Android.mk中的LOCAL_C_INCLUDES
"libnativehelper/include/nativehelper",
"system/bt/types",
],
shared_libs: [ //编译所依赖的动态库,类似于Android.mk中的LOCAL_SHARED_LIBRARIES
"libandroid_runtime",
"libchrome",
"libnativehelper",
"libcutils",
"libutils",
"liblog",
"libhardware",
],
static_libs: [ //编译所依赖的静态库,类似于Android.mk中的LOCAL_STATIC_LIBRARIES
"libbluetooth-types",
],
cflags: [ ///编译flag,类似于Android.mk中的LOCAL_CFLAGS
"-Wall",
"-Wextra",
"-Wno-unused-parameter",
],
}
上述模块类型为二进制可执行文件,如果要编译一个APP,那么使用android_app模块,等等。模块类型在android/build/soong/androidmk/cmd/androidmk/android.go中可以查到
模块类型后面用大括号{} 将模块的所有属性包裹起来。
●查看全部支持的模块和各个模块支持的属性定义,请查看这个网址:https://ci.android.com/builds/submitted/6504066/linux/latest/view/soong_build.html。
●cc_defaults模块比较特殊,它表示该模块的属性可以被其他模块重复引用,类似于我们的头文件被其他cpp文件引用,举例:
cc_defaults {
name: "gzip_defaults",
shared_libs: ["libz"],
stl: "none",
}
cc_binary {
name: "gzip",
defaults: ["gzip_defaults"],
/*这里等价于
shared_libs: ["libz"],
stl: "none",*/
srcs: ["src/test/minigzip.c"],
}
●属性可以使用列表数组的形式,也可以使用unix通配符,例如:”*.java”
●每一条完整的属性定义语句加上逗号“,”表示结束
●注释包括单行注释//和多行注释/**/
●最重要的一点:目前android编译系统同时支持mk和bp两种,但是这两种是彼此单独运行的,所以bp中依赖的目标,例如动态库静态库目标,如果库是源代码的形式存在的,那么库的编译脚本必须也是通过bp文件编译才能被找到,否则用mk文件编译库,bp会提示找不到依赖的库目标。(paxdroid代码中因为要将framework相关内容编译到android的framework中,而android已经全部转还成bp,所以这一部分paxdroid也是使用的bp来编写的,不然会提示找不到)
更多的说明可以在android/build/soong/README.md文件中查找。如果找不到想要的,可以在官网找:https://ci.android.com/builds/submitted/6504066/linux/latest/view/soong_build.html
变量
变量可以直接定义,使用'' =''赋值
avbctl_srcs = [“tools/avbctl/avbctl.cc”],
cc_binary {
name: ”avbctl”,
defaults: [“avb_defaults”],
static_libs: [
“libavb_user”,
“libfs_mgr”,
],
shared_libs: [“libbase”],
srcs: avbctl_srcs,
}
条件编译�
例如在.mk文件中条件判断:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := fs_mgr
ifeq ($(ENABLE_USER2ENG),true)
LOCAL_CFLAGS += -DALLOW_ADBD_DISABLE_VERITY=1
LOCAL_CFLAGS += -DENABLE_USER2ENG=1
endif
LOCAL_CFLAGS += -Wno-error=implicit-function-declaration
ifeq ($(shell if [ -d $(TOPDIR)paxdroid/external/libethtool ]; then echo "exist"; else echo "notexist"; fi;), exist)
LOCAL_SHARED_LIBRARIES +=libethtool
endif
include $(BUILD_EXECUTABLE)
上述释义:如果变量ENABLE_USER2ENG的值为true,那么追加这两个编译参数,否则不追加。第二个条件是说如果存在paxdroid/external/libethtool这个目录,那么就添加libethtool这个动态库,否则不添加。
.bp 写法:
需要我们通过go语言写一个新文件,新建一个自定义类型的模块,来判断这些条件,比如我的修改是在system/core/fs_mgr/Android.bp,那么要在添加 system/core/fs_mgr/fs_mgr.go
//这是申明当前的包名,就是你在哪个文件夹下,就写啥
package fs_mgr
//导入android的soong工具目录
import (
"android/soong/android"
"android/soong/cc"
"fmt"
//一般情况下不需要引用os包,这里要判断文件夹是否存在需要引用
"os"
)
//初始化入口函数
func init() {
// for DEBUG
fmt.Println("init start")
//注册模块名:fs_mgr_condition,模块名的方法入口是:fs_mgrDefaultsFactory方法
android.RegisterModuleType("fs_mgr_condition", fs_mgrDefaultsFactory)
}
//实现fs_mgrDefaultsFactory方法:
func fs_mgrDefaultsFactory() (android.Module) {
//如果我们编译的目标是bin文件,这里就是调用cc.DefaultsFactory()
//如果编译目标是so库,那么调用的是cc.LibrarySharedFactory()。这里我们举例目标是bin文件:
module := cc.DefaultsFactory()
//添加装载时的钩子函数fs_mgrDefaults
android.AddLoadHook(module, fs_mgrDefaults)
return module
}
//实现钩子函数
func fs_mgrDefaults(ctx android.LoadHookContext) {
//这里定义我们所有需要受到条件控制的变量,比如我们这里需要根据条件来控制Cflags和依赖
//的动态库两个变量,所以我们只需要定义这两个即可,按照实际需求定义:
type props struct {
Cflags []string
Shared_libs []string
}
p := &props{}
p.Cflags = getCflags(ctx)
p.Shared_libs = getShared_libs(ctx)
ctx.AppendProperties(p)
}
//实现getCflags(ctx)方法
func getCflags(ctx android.BaseContext) ([]string) {
var cppflags []string
fmt.Println("ENABLE_USER2ENG:",
ctx.AConfig().IsEnvTrue("ENABLE_USER2ENG"))
if ctx.AConfig().IsEnvTrue("ENABLE_USER2ENG")
{
cppflags = append(cppflags, "-DALLOW_ADBD_DISABLE_VERITY=1", "-DENABLE_USER2ENG=1")
}
return cppflags
}
//实现getShared_libs(ctx)方法
func getShared_libs(ctx android.BaseContext) ([]string) {
var shared_libs []string
//判断文件是否存在,该方法返回两个参数,一个isExists,一个error
isExists,error := PathExists ("paxdroid/external/libethtool")
if(isExists && error == nil){
//路径存在
shared_libs = append(shared_libs, “libethtool”)
}
return shared_libs
}
//实现判断文件夹是否存在的工具方法:
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
相应的再Android.bp文件引用,如下
// 这些个必须添加,编译刚刚写的那个go脚本需要的一些依赖
bootstrap_go_package {
// 名字和包路径和刚刚写的go文件一致
name: "soong-fs_mgr",
pkgPath: "android/soong/fs_mgr",
deps: [
"blueprint",
"blueprint-pathtools",
"soong",
"soong-android",
"soong-cc",
"soong-genrule",
],
//这里的srcs就写我们刚刚写的go脚本
srcs: [
"fs_mgr.go",
],
pluginFor: ["soong_build"],
}
// fs_mgr_condition 是我们在go语言中自定义的模块类型,模块的类型是fs_mgr_condition,
// 这个模块名字叫做:fs_mgr_defaults
fs_mgr_condition {
name: "fs_mgr_defaults",
}
//-----------------------------------------------------------------------
//以上所有代码是标准的添加自定义模块来实现条件控制的代码,我们可以记下来作为参考,
//下面的代码才是我们编译具体模块的:
cc_binary {
name: "fs_mgr",
//这里引用上面定义的模块
defaults: ["fs_mgr_defaults"],
cppflags: ["-Wno-error=implicit-function-declaration "],
}
操作符
String类型、字符串列表类型和Map类型支持操作符“+”,例如:
binder_src_files = ["lib/libsystool_client/binder.c"],
cc_library_static {
srcs: ["lib/libsystool_client/systool_client_static.c",
"ipc/pipe_client.c",
] + binder_src_files,
}