Android.mk
Android.mk | Android NDK | Android Developers (google.cn)
Makefile导入
动态库静态库
Android.mk
决定编译
Android.mk解析
Android.mk 是 Android 提供的一种 Makefile 文件,属于 GUN makefile 的一部分,会被编译系统解析一次或多次,因此我们应尽量少的在 Android.mk 中声明变量,也不要假定任何东西不会在解析过程中定义。
在源码树中每一个模块的所有文件通常都相应有一个自己的文件夹,在该模块的根目录下有一个名称为“Android.mk” 的文件。
-
build/core/main.mk
将所有的Android.mk
添加进编译系统。 -
编译整个工程的情况下,
- 系统所找到的所有
Android.mk
将会先存入subdir_makefiles
变量, - 随后一次性 include进整个编译文件中
- 系统所找到的所有
#
# Include all of the makefiles in the system
#
subdir_makefiles := $(SOONG_ANDROID_MK) $(file <$(OUT_DIR)/.module_paths/Android.mk.list) $(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)))
编译系统正是以模块为单位进行编译,每个模块都有唯一的模块名,一个模块可以有依赖多个其他模块,模块间的依赖关系就是通过模块名来引用的。
也就是说当模块需要依赖一个jar包或者apk时,必须先将jar包或apk定义为一个模块,然后再依赖相应的模块。
模块
一个模块。(具体的做一道菜)
- 准备食材,按照android编译系统的需求买菜。
包括LOCAL_SRC_FILES
,LOCAL_MODULE
,LOCAL_STATIC_LIBRARIES
等的收集。
- 烹饪。Google封装了不同菜的烹饪方式,提供了各种模板。
BUILD_STATIC_LIBRARY
,PREBUILT_SHARED_LIBRARY
,PREBUILT_EXECUTABLE_LIBRARY
每个 Android.mk
,允许有多个模块。(允许做多道菜)
例如:下列mk中包含了,两个模块。
- 模块一:源码第2行至第9行,用于编译一个java类库
- 模块二:源码第12行至第26行,用于编译一个apk安装包
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)
# ---------------第二道菜做完了-------------------------- #
语法
赋值,打印
赋值:=
- 给变量
LOCAL_PATH
赋值为函数my-dir
的返回值LOCAL_PATH := $(call my-dir)
赋值+=
-
给变量
LOCAL_PATH
赋值为函数my-dir
的返回值LOCAL_PATH += $(call my-dir)
LOCAL_SHARED_LIBRARIES := liblog libutils libcutils LOCAL_SHARED_LIBRARIES += libqdMetaData libgrallocutils
打印
$(info "打印内容")
- 打印中调用变量的值
$(info "...$变量名...")
$(info "LOCAL_PATH:======== ${LOCAL_PATH}")
宏函数 $(call xxx)
使用
-
已知宏函数
my-dir
-
调用宏函数
有哪些
针对这些环境变量,编译系统还定义了一些 便捷函数/宏函数,如下:
$(call my-dir)
:获取当前文件夹(包含Android.mk
文件本身的目录)路径;$(call all-java-files-under,)
:获取指定目录下的所有Java文件;(不指定则默认my-dir
当前目录)
$(call all-c-files-under,)
:获取指定目录下的所有C文件;(不指定则默认my-dir
当前目录)$(call all-Iaidl-files-under,)
:获取指定目录下的所有AIDL文件;(不指定则默认my-dir
当前目录)$(call all-makefiles-under,)
:获取指定目录下的所有Make文件;(不指定则默认my-dir
当前目录)
module属性
为方便模块编译,编译系统设置了很多的编译环境变量(LOCAL_XXX
变量),如下:
名称,路径
路径
-
LOCAL_PATH
此变量用于指定源码所在的路径。- 最好放在CLEAR_VARS变量引用的前面,它不会被清除
- 每个Android.mk只需定义一次即可
# 指定源码路径为: 当前路径 LOCAL_PATH := $(call my-dir)
-
LOCAL_MODULE_PATH
:模块的输出路径
名称
模块、库
-
LOCAL_MODULE
:当前 模块、库 的名称(具有唯一性);- 不可以有空格
- 需要保证在整个编译系统中,唯一存在不重复
LOCAL_MODULE := settings-logtags
-
LOCAL_MODULE_FILENAME
:替换构建系统为其生成的文件默认使用的名称LOCAL_MODULE := settings-logtags LOCAL_MODULE_FILENAME := settingslib
apk属性
-
LOCAL_PACKAGE_NAME
:指定编译后生成的Android APK的名字,当前APK应用的名称(具有唯一性);LOCAL_PACKAGE_NAME := SettingsAPKName
-
LOCAL_CERTIFICATE
:指定APK的签名方式。如果不指定,默认使用testkey签名。Android中共有四中签名方式:
-
testkey:普通APK,默认使用该签名。
-
platform:该APK完成一些系统的核心功能。经过对系统中存在的文件夹的访问测试,这种方式编译出来的APK所在进程的UID为system。
-
shared:该APK需要和home/contacts进程共享数据。
-
media:该APK是media/download系统中的一环。
-
LOCAL_CERTIFICATE := platform
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
文件
src
-
LOCAL_SRC_FILES
:当前模块编译过程中所涉及的所有源文件;可以通过编译系统定义的 便捷函数/宏函数,实现:
-
$(call all-java-files-under,)
:获取指定目录下的所有Java文件;(不指定则默认my-dir
当前目录) -
$(call all-c-files-under,)
:获取指定目录下的所有C文件;(不指定则默认my-dir
当前目录)
LOCAL_SRC_FILES := PxlwIrisHwService.cpp LOCAL_SRC_FILES += src/IrisService.cpp \ src/IrisHWC.cpp
-
include
-
LOCAL_C_INCLUDES
: 用户指定的C/C++头文件查找路径LOCAL_C_INCLUDES := $(TOP)/vendor/qcom/opensource/commonsys-intf/display/gralloc LOCAL_C_INCLUDES += $(TOP)/vendor/qcom/opensource/commonsys-intf/display/qdMetaData
拷贝
以下二者交替使用
LOCAL_COPY_HEADERS
,安装应用程序时所需复制的头文件列表LOCAL_COPY_HEADERS_TO
,头文件复制的目的地
编译 链接
编译
宏
-
LOCAL_CFLAGS
: 指定C宏 -
LOCAL_CXXFLAGS
: 指定C++宏
编译器
-
LOCAL_CC
: 指定C编译器 -
LOCAL_CXX
: 指定C++编译器 -
LOCAL_CPP_EXTENSION
指定特殊的C++文件名后缀(例如 cpp)
链接库
选项
编译时所需的链接选项
标签
-
LOCAL_MODULE_TAGS
:当前模块所包含的标签,可以包含多标签,可能值为debgu,eng,user,development或optional(默认值)LOCAL_MODULE_TAGS := optional
导入 log
-
LOCAL_LDLIBS
LOCAL_LDLIBS:= -lm -llog
native
-
静态库的链接
LOCAL_STATIC_LIBRARIES
:当前模块在静态链接时需要的库名;LOCAL_STATIC_LIBRARIES := libjsoncpp libtinyxml2
-
动态库的链接
LOCAL_SHARED_LIBRARIES
:当前模块在运行时依赖的动态库名;LOCAL_SHARED_LIBRARIES := liblog libutils libcutils LOCAL_SHARED_LIBRARIES += libqdMetaData libgrallocutils
-
头文件库
LOCAL_HEADER_LIBRARIES
java
-
Java静态库
LOCAL_STATIC_JAVA_LIBRARIES
:当前模块依赖的Java静态库; -
Java共享库
LOCAL_JAVA_LIBRARIES
:当前模块依赖的Java共享库;LOCAL_JAVA_LIBRARIES := com.gityuan.lib
module类型
各种形式的编译模板。
清理 CLEAR_VARS
include $(CLEAR_VARS)
CLEAR_VARS 变量指向特殊 GNU Makefile,可清除许多 LOCAL_XXX 变量
会清除许多 LOCAL_XXX
变量,但不会清除 LOCAL_PATH
。此变量必须保留其值,因为系统在单一 GNU Make 执行上下文(其中的所有变量都是全局变量)中解析所有构建控制文件。
在描述每个模块之前,必须重新声明此变量。
native
include $(CLEAR_VARS)
# # # 中间内容...
include $(BUILD_STATIC_LIBRARIES) # 构建native静态库
include $(BUILD_SHARED_LIBRARIES) # 构建native动态库
Prebuilt 预编译
参数
Android提供了Perbuilt编译方式,处理已经编译好的库或配置文件
- 把已经编译好的文件,作为一个模块:
- Prebuilt 不考虑编译源文件的情况。
include $(CLEAR_VARS)
# # # 中间内容...
# 引入
# prebuild 共享库
include $(PREBUILT_SHARED_LIBRARY)
# prebuild 静态库
include $(PREBUILT_STATIC_LIBRARY)
# 指定文件(也是制作库?不知道区别)
# # prebuilt.mk
include $(BUILD_PREBUILT)
# # multi_prebuilt.mk
include $(BUILD_MULTI_PREBUILT)
制作预编译文件,有两种方式:
- 复制(常用)
- 直接使用
其中使用到的参数:
LOCAL_SRC_FILES
: 源文件LOCAL_MODULE
: 拷贝后的文件名LOCAL_MODULE_PATH
: 拷贝的路径(可以在源文件处写绝对路径从而省略)SHARED_LIBRARIES
(lib)、EXECUTABLES
(bin)、LOCAL_MODULE_CLASS
: APPS(apk文件)、ETC(其他文件)
例子
native例子
制作预编译文件的例子:
include $(CLEAR_VARS)
LOCAL_MODULE := libavcodec # 在libavcodec模块中,放入prebuild预编译文件
LOCAL_SRC_FILES := ffmpeg/android-libs/$(TARGET_ARCH_ABI)/$(LOCAL_MODULE).so
include $(PREBUILT_SHARED_LIBRARY)
使用预编译文件的例子:
include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg # 在ffmpeg模块中,通过包含prebuild预编译文件的模块libavcodec,使用prebuild预编译文件
...
LOCAL_SHARED_LIBRARIES := libavcodec # 把引入的已经编译好的prebuild预编译文件,加入会使用到的ffmpeg模块中
...
include $(BUILD_SHARED_LIBRARY) # ffmpeg模块是一个共享库
java例子
制作预制类库
# opencv library 预制类库
include $(CLEAR_VARS)
# # 1 给multi_prebuilt.mk引入java库
# # 2 取别名 ctsverifier-opencv,
# # 3 指定contextualcards的实际路径
# # 这些库一般可以指定在相对路径的lib文件夹下。例如这里的libs/opencv3-android.jar
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
ctsverifier-opencv:libs/opencv3-android.jar
include $(BUILD_MULTI_PREBUILT)
使用预制类库
include $(CLEAR_VARS)
LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 \
ctsverifier-opencv \
...
-include cts/error_prone_rules_tests.mk
include $(BUILD_PACKAGE)
other
java
include $(CLEAR_VARS)
# # # 中间内容...
include $(BUILD_JAVA_LIBRARY)
用于构建java类库,
-
该类库中的代码会以dex的形式存在
-
生成的Java Library既不会被安装,也不会被放入Java的Classpath中。
include $(CLEAR_VARS)
# # # 中间内容...
include $(BUILD_STATIC_JAVA_LIBRARY)
用于构建java类库,
-
该类库中代码会以class文件的形式存在.
-
如果编译出jar是用于app的开发,应该使用该变量描述。
-
生成的Java 库,具有"共享"性质,可以被多个程序所共用。(复制到
/system/framewo
)
app安装包
在实际开发中,我们经常需要在app中引入lib文件夹下的第三方的jar或aar,在Android.mk中,可以按照如下的方式描述:
用于构建Android 应用程序安装包。
include $(CLEAR_VARS)
# # # 中间内容...
include $(BUILD_PACKAGE)
指定分区
System 分区:
# 不需要在 Android.mk/Android.bp 中额外指定,默认就是system 分区
预制系统源码到 System 分区
PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST 白名单
Vendor 分区:
# Android.mk
LOCAL_VENDOR_MODULE := true
Odm 分区:
# Android.mk
LOCAL_ODM_MODULE := true
Product 分区:系统源码中程序一般预制到 Product 分区
# Android.mk
LOCAL_PRODUCT_MODULE := true
Android.mk示例
编译 Login.c
Test.c
# 1.源文件在的位置
LOCAL_PATH := $(call my-dir) # 返回Android.mk文件当前目录
$(info "LOCAL_PATH:======== ${LOCAL_PATH}") # 打印
# 重新声明
include $(CLEAR_VARS)
# 会被清理掉的LOCAL_XXX变量
LOCAL_MODULE := getndk # module名
LOCAL_SRC_FILES := libgetndk.so # 动态库名 lib模块名.so
# LOCAL_SRC_FILES := libgetndk.a # 静态库名 lib模块名.a
# 2.预编译库的引入(提前编译好的库)
# 2.1 预编译
# # 预编译静态库的Makeifle脚本
# include $(PREBUILT_STATIC_LIBRARY)
# # 预编译共享库的Makeifle脚本
include $(PREBUILT_SHARED_LIBRARY)
# 2.2 其他makefile文件
# # 清理。 CLEAR_VARS 变量指向特殊 GNU Makefile,
include $(CLEAR_VARS) #可清除许多 LOCAL_XXX 变量,不会清理 LOCAL_PATH 变量
# 3.LOCAL变量
# 3.1指定库名字
#存储要构建的模块的名称 每个模块名称必须唯一,且不含任何空格
#如果模块名称的开头已是 lib,则构建系统不会附加额外的前缀 lib;而是按原样采用模块名称,并添加.so 扩展名。
LOCAL_MODULE := MyLoginJar # 指定库名 module名
# 3.2源文件列表
# # 会构建到模块中的 C 和/或 C++ 源文件列表 以空格分开
LOCAL_SRC_FILES := Login.c \
Test.c
# 3.3 链接
# # 静态库的链接
# LOCAL_STATIC_LIBRARIES := getndk
# # 动态库链接
LOCAL_SHARED_LIBRARIES := getndk
# 3.4 导入 log
#LOCAL_LDLIBS := -llog
LOCAL_LDLIBS := -lm -llog
# 4.根据编译模板,生成总动态库
#构建动态库BUILD_SHARED_LIBRARY 最后生成总动态库 ---> apk/lib/armeabi-v7a/libMyLoginJar.so
include $(BUILD_SHARED_LIBRARY)
soong_config
namespace
是什么
Soong命名空间是什么?
在 Soong 可以让不同目录中的模块,指定相同的名称:每个模块都在单独的命名空间中声明。
import
命名空间没有 name
属性;其路径会自动指定为其名称
// vendor/pixelworks/libirisservice/Android.bp中
// Use soong_namespace only when Android is verison 11 (R).
soong_namespace {
imports: [
"hardware/qcom/display",
"hardware/qcom/display/gralloc",
],
}
// 对应于
// - hardware/qcom/display/Android.bp
soong_config_module_type {
name: "iris_libdrmutils_cc_defaults",}
// - hardware/qcom/display/gralloc/Android.bp
soong_config_module_type {
name: "..."}
declare
每个 Soong 模块都会被视为处于 Android.bp定义的命名空间中。
-
未显式声明:
-
视为最近的父级目录中的
soong_namespace
-
父级目录中也未找到此类 soong_namespace 模块
认为该模块位于隐式根命名空间中。
-
-
显式地声明
- 当前module,以及通过default属性指定的其它module
- 寻找 config_namespace 属性
cc_library_shared { // 在module设置defaults属性为其它module name: "libpwirisfeature", defaults: ["iris_libpwirisfeature_defaults"],} iris_libpwirisfeature_cc_defaults { // 去找对应soong_config name: "iris_libpwirisfeature_defaults", // ... } soong_config_module_type { // 定义了 config_namespace name: "iris_libpwirisfeature_cc_defaults", // ... config_namespace: "pxlw_iris", }
查找顺序
Soong 尝试解析由模块 M 在名称空间 N(导入命名空间 I1、I2、I3…)中声明的依赖项 D。
-
如果 D 是 //namespace:module 格式的完全限定名称,系统将仅在指定的命名空间中搜索指定的模块名称。
-
否则,Soong 将首先查找在命名空间 N 中声明的名为 D 的模块。
-
如果该模块不存在,Soong 会在命名空间 I1、I2、I3…中查找名为 D 的模块。
-
最后,Soong 在根命名空间中查找。
soong_config 编译过程
在编译的时候(soong_config_modules.go 中解析)会统计所有的 soong_config_module_type
得到 soong_config_module_type后,Android.bp 的配置一般分为三部分:
soong_config_module_type {
# 指定config 配置(指定module名)
name: "iris_irisconfig_cc_defaults",
}
soong_module
soong_config_module_type 是 bp 配置config 的核心。
作用是声明 module的属性:指定一个默认的moduel,名称空间,module类型,配置类别,等等…
// 声明
soong_config_module_type {
name: "iris_irisconfig_cc_defaults",
module_type: "cc_defaults",
config_namespace: "pxlw_iris",
bool_variables: [
"enable_aidl",
],
properties: [
"cflags",
"shared_libs",
],
}
// 引用
soong_config_module_type_import {
from: "frameworks/native/services/surfaceflinger/Android.bp",
module_types: ["libdisplayconfig_cc_defaults"],
}
具体来说,声明的类型有
-
name
指定config 配置。-
后续会从同名 module 中,获取信息。
-
一般命名为,
库名_module_type
上述
iris_irisconfig_cc_defaults
,cc_defaults就是module_type
-
-
module_type
库的类型 -
config_namespace
名称空间-
config 配置的namespace,必须要指定
-
名称空间的意义。
在makefile中已经定义了namespace,以namespace为单位,管理模块
# vendor\pixelworks\libirisservice\config\pixelworks_iris.mk # Soong Namespace SOONG_CONFIG_NAMESPACES += pxlw_iris
-
-
变量声明
-
variables:
注册所有的变量
例如:
bool_variables
-
properties:
变量需要涉及的Android.bp的property
-
cc_defaults
vendor/pixelworks/libirisservice/Android.bp
// Use cc_defaults when libirisservice.go is not used.
iris_irisconfig_cc_defaults {
name: "iris_irisconfig_defaults",
soong_config_variables: {
enable_aidl: {
cflags: ["-DPXLW_AIDL_SUPPORT"],
shared_libs: ["libbinder_ndk"],
},
}
}
变量配置的主要项,
-
module名
- 对应:
soong_config_module_type
中name="xxx"
声明的module名 - 在这里是
iris_irisconfig_cc_defaults
- 对应:
-
name 模块名
-
name: "iris_irisconfig_defaults"
-
通过
name
在编译时引用该 module例如
irisConfig
的编译目标时,引用该 module
-
-
soong_config_variables属性
:-
实现
soong_config_module_type
中声明 module 时,声明 module会有的变量 bool_variables , properties -
在这里是
enable_aidl
-
这里的 cflags意味着,需要用到
PXLW_AIDL_SUPPORT
这个宏,但这个宏实际上在别处定义。# vendor\pixelworks\libirisservice\config\pixelworks_iris.mk # Enable IIris AIDL and IIrisFeature AIDL PXLW_AIDL_SUPPORT := false
-
-
最终产物 module
cc_library
cc_library_shared
等,使用到 之前定义的module设置。
// Build libpwirisService
cc_library_shared {
name: "libpwirisservicei7",
defaults: ["pwirisservice_defaults"],
}
// Build irisConfig executable file
cc_binary {
name: "irisConfig",
defaults: ["iris_irisconfig_defaults"],
}
- 其它属性,照旧
defaults
属性- 指定一个/多个默认 module
- 对应于 module的默认属性,没有的属性,采用该 module的内容。
Android.bp
Android.bp快速入门 - 掘金 (juejin.cn)
概念
Android.bp 文件中的模块以模块类型开头,然后是一组格式属性:name: value,在一点上Android.bp的语法结构与JSON的语法结构相似,都是以键值对的形式编写。
为啥出现
随着 android 工程越来越大,包含的 module 越来越多,以 makefile 组织的项目编译花费的时间越来越多。Google 在 7.0 引入了 ninja 是 Google 的一名程序员推出的注重速度的构建工具,一般在 Unix/Linux 上的程序通过 make/makefile 来构建编译,而 Ninja 通过将编译任务并行组织,大大提高了构建速度。
实际上,编译android源码时,soong也会被自动使用:m、mm、mmm或者make最终都会使用soong来编译。
-
7.0 的时候
Soong 构建系统是在 Android 7.0 (Nougat) 中引入的,旨在取代 Make
Soong
包含两大模块,Kati
负责解析Makefile
并转换为.ninja
,Ninja
则基于生成的.ninja
完成编译。
Soong编译系统下,原本打算输入是.bp文件,输出是.ninja文件,但是由于系统中的.mk文件还没有被完全消除掉,
android 项目还是有大部分是由 makefile 来组织的,因此 Google 引入了 kati和ckati工具将 makefile 翻译成 ninja 文件。
-
8.0 开始,android 引入了 Android.bp 来替代 之前的 Android.mk 文件,
不同于Android.mk,Android.bp只是纯粹的配置文件,不包括分支、循环等流程控制。 -
在 android 项目上如何进行选择编译、解析配置、转换成 ninja 等,Soong 就被创造出来,将 Android.bp 转换为ninja文件进行管理。
Android.bp 文件中的模块以模块类型开头,然后是一组格式属性:name: value,在一点上Android.bp的语法结构与JSON的语法结构相似,都是以键值对的形式编写
转换
-
Android.mk 翻译成 ninja,
Soong的kati模块。
kati是专为Android开发的一个基于Golang和C++的工具,主要功能是把Android中的Makefile和.mk文件(Android.mk)转换成Ninja文件。代码路径是build/kati/,编译后的产物是ckati
-
bp 翻译成 ninja
- soong集成了Blueprint,Blueprint只是解析文件格式,
- Soong解析内容的具体含义
-
Soong 包含了一个工具
androidmk
,可以将 Android.mk 文件转换为 Android.bp 文件:工具位于:
android\out\soong\host\linux-x86\bin\androidmk
该工具可以转换变量,模块,注释和一些条件,但任何自定义的 Makefile 规则,复杂条件或额外的 include 必须手动转换。
androidmk Android.mk > Android.bp
soong
编译
在android 6.0版本之前,编译android源码采用的是基于make的编译系统(make-based build system),
- android的各个库、APK等等目标文件都是采用make来构建的
- make在编译时表现出效率不够高、增量编译速度慢等问题,
在android 7.0版本引进了编译速度更快的soong来替代make。
-
编译android源码时,soong也会被自动使用:
-
m、mm、mmm或者make最终都会使用soong来编译。
-
source build/envsetup.sh。
-
lunch选择target。
-
使用m、mm、mmm或者make来编译指定的模块或者整个系统。
-
功能
soong:选择转换工具、选择解析框架、解析维护构建逻辑的“管家”就是。
-
集成了Ninja,Blueprint, kati等等好几种工具。为了完整、快速的构建一个android系统,
-
mk bp 转换到
ninja
- 将
.bp
转换成ninja
时使用Blueprint
- 将
Makefile
转换成ninja
时使用kati
- 将
-
将 Android.mk 文件转换为 Android.bp 文件:
-
androidmk
androidmk Android.mk > Android.bp
-
-
格式化Android.bp文件
-
bpfmt
规范格式包括:- 4个空格缩进。
- 多元素列表的每个元素后的换行符。
- 在lists和maps的结尾处始终包含一个逗号。
# 递归地格式化当前目录中的所有Android.bp文件 bpfmt -w .
-
module属性
模块包含一些属性格式为 property-name:property-value
的键值对。
其中默认模块可用于在多个模块中重复相同的属性。
名称 name
名称
- name : 编译出的模块模块的名称,必须指定,其属性值必须是全局唯一的。
name: "pwirisservice_defaults",
文件
源文件
模块的源文件,用于指定当前的模块编译的源码位置
-
srcs
具体的包含哪些源文件
-
使用相对路径(相对该Andoid.bp而言)
*
表示通配符 。 -
可以直接解析
filegroup
变量
-
cc_library_shared {
name: "libpwirisservicei7",
// ...
srcs: [
":pwiris_service_sources", // 调用如下 filegroup 中的文件
"src/*.cpp",
],
}
// 对应vendor/pixelworks/libirisservice/common/service/Android.bp
filegroup {
name: "pwiris_service_sources",
srcs: [
"src/*.cpp",
],
}
头文件
指定的头文件查找路径
-
include_dirs 绝对路径
-
local_include_dirs 相对路径(相对该Andoid.bp而言)
// 指定头文件查找路径
// - (绝对路径)
include_dirs: [
"frameworks/base/libs/hwui",
"external/skia/include/private",
],
// - (相对路径)
// frameworks/native/services/surfaceflinger/CompositionEngine/Android.bp
// 查找的头文件也就是Android.bp同路径下的 include 文件夹
cc_library {
name: "libcompositionengine",
local_include_dirs: ["include"],
Android.bp
包含子文件夹下的Android.bp
subdirs
一个文件级的顶层属性- 指定后会查找次级目录下的Android.bp
// vendor/pixelworks/libirisconfig/Android.bp
subdirs = [
"app",
"irisfeaturehal",
"irishal",
]
// 对应的会去查找
// - vendor/pixelworks/libirisconfig/app/Android.bp
// - vendor/pixelworks/libirisconfig/irisfeaturehal/Android.bp
// - vendor/pixelworks/libirisconfig/irishal/Android.bp
编译 链接
编译
宏
- cflags:类似于Android.mk中的LOCAL_CFLAGS。
cc_library_shared {
name: "libpwirisservicei7",
cflags: [
"-DPXLW_IRIS_DUAL",
"-DPQ_TARGET_SWITCH",
"-DUSE_QCOM_QSERVICE",
"-DUSE_QCOM_COLOR_METADATA",
"-DLOG_TAG=\"IRIS_LOG_SERV_I7\"",
]
}
链接
编译时依赖的库
shared_libs
: 动态库static_libs
: 静态库header_libs
: 头文件库
cc_library_shared {
name: "vendor.pixelworks.hardware.display@1.0-impl-1.2-i7",
shared_libs: [ // 编译时依赖的动态库
"libpwirisservicei7",
],
}
pwirisservice_defaults {
name: "pwirisservice_defaults",
static_libs: [ // 编译时依赖的静态库
"libjsoncpp",
"libtinyxml2",
],
}
module类型
定义一个模块,从模块的类型开始,模块有不同的类型,
native 库
声明编译成:
# 静态库
# # 类似Android.mk BUILD_STATIC_LIBRARY
cc_library_static {...}
# 动态库
# # 类似Android.mk BUILD_SHARED_LIBRARY
cc_library_shared {...}
# 可执行文件
# # 类似Android.mk BUILD_EXECUTABLE
cc_binary {...}
prebuild
cc_prebuilt_library_shared
cc_prebuilt_library_static
cc_prebuilt_binary
java_import
prebuilt_etc
动态库举例
cc_prebuilt_library_shared
- multilib
cc_prebuilt_library_shared {
name: "libpwiriscalibrate",
export_include_dirs: [
"obj/include",
],
multilib: {
lib32: {
srcs: [
"lib/libpwiriscalibrate.so",
],
},
lib64: {
srcs: [
"lib64/libpwiriscalibrate.so",
],
},
},
vendor: true,
shared_libs: [
"liblog",
"libutils",
"libcutils",
"libpwsoftirisPCS",
],
}
第三方jar
java_import
在实际开发中,经常需要在app中引入第三方的jar。在Android.bp中,引入第三方的jar可以按照下面的方式。
首先,在项目的根目录新建 libs文件夹,放入要导入的jar包,比如 CarServicelib.jar,然后在Android.bp中引入该jar。
java_import {
name: "CarServicelib.jar",
jars: ["libs/CarServicelib.jar"],
}
android_app {
// 省去其它属性
static_libs: [
"CarServicelib.jar"
],
}
其它 库
java 库
-
java_library
:java用的jar
android
-
android_library
-
android_app
开发过程中,如果想要引入AndroidX的类库,可以参考下面的方式进行引入。
android_app {
// 省去其它属性
// 引入AndroidX库下的lib
static_libs: [
"androidx.cardview_cardview",
"androidx.recyclerview_recyclerview",
"androidx-constraintlayout_constraintlayout"
],
}
基础语法
设置全局变量 =
在bp中可以通过=号来设定一个全局变量。
src_path = ["**/*.java"]
android_app {
name: "Provision",
srcs: src_path,
数据类型
Android.bp中变量和属性是强类型,变量根据第一项赋值动态变化,属性由模块类型静态设置,支持的类型为:
- 布尔值
true
或false
- 整数
int
- 字符串
"string"
- 字符串列表
["string1", "string2"]
- 映射
{key1: "value1", key2: ["value2", "value3"]}
条件语句(不支持)
Soong 不支持 Android.bp 文件中的条件语句。
编译规则中如果需要处理条件语句,那么需要在 Go中进行处理。
大多数条件语句都会转换为映射属性,其中选择了映射中的某个值并将其附加到顶级属性。
例如,要支持特定的架构文件,可以使用以下命令:
cc_library {
...
srcs: ["generic.cpp"],
arch: {
arm: {
srcs: ["arm.cpp"],
},
x86: {
srcs: ["x86.cpp"],
},
},
}
运算符 +
可以使用 + 运算符:
-
附加字符串、字符串列表和映射。
-
对整数求和。
-
附加映射,生成两个映射中键的并集,并附加在两个映射中都存在的所有键的值。
指定分区
System 分区:
# 不需要在 Android.mk/Android.bp 中额外指定,默认就是system 分区
预制系统源码到 System 分区
PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST 白名单
Vendor 分区:
# Android.bp
vendor: true
Odm 分区:
# Android.bp
device_specific: true
Product 分区:系统源码中程序一般预制到 Product 分区
# Android.bp
product_specific: true
示例
动态库
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"
],
include_dirs: [ //用户指定的头文件查找路径,类似于Android.mk中的LOCAL_C_INCLUDES
"libnativehelper/include/nativehelper",
"system/bt/types",
],
shared_libs: [ //编译所依赖的动态库,类似于Android.mk中的LOCAL_SHARED_LIBRARIES
"libandroid_runtime",
"libchrome",
],
static_libs: [ //编译所依赖的静态库,类似于Android.mk中的LOCAL_STATIC_LIBRARIES
"libbluetooth-types",
],
cflags: [ //编译flag,类似于Android.mk中的LOCAL_CFLAGS
"-Wall",
"-Wextra",
"-Wno-unused-parameter",
],
}
subdirs = ["subdir1", "subdir2"]
可执行程序
Android.bp位于Android 10 : packages/apps/Car/Notification 目录下,参考示例如下:
// 构建可执行程序
android_app {
// 设定可执行的程序的名称,编译后会生成一个 CarNotification.apk
name: "CarNotification",
// 指定java源码的位置
srcs: ["src/**/*.java"],
// 指定资源文件的位置
resource_dirs: ["res"],
// 允许使用系统hide api
platform_apis: true,
// 设定apk签名为 platform
certificate: "platform",
// 设定apk安装路径为priv-app
privileged: true,
// 是否启用代码优化,android_app中默认为true,java_library中默认为false
optimize: {
enabled: false,
},
// 是否预先生成dex文件,默认为true。该属性会影响应用的首次启动速度以及Android系统的启动速度
dex_preopt: {
enabled: false,
},
// 引入java静态库
static_libs: [
"androidx.cardview_cardview",
"androidx.recyclerview_recyclerview",
"androidx.palette_palette",
"car-assist-client-lib",
"android.car.userlib",
"androidx-constraintlayout_constraintlayout"
],
// 引入java库
libs: ["android.car"],
product_variables: {
pdk: {
enabled: false,
},
},
// 设定依赖模块。如果安装了此模块,则要还需要安装的其他模块的名称
required: ["privapp_whitelist_com.android.car.notification"]
}
// As Lib
android_library {
name: "CarNotificationLib",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
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"],
product_variables: {
pdk: {
enabled: false,
},
},
}
宏控
Android.mk
直接添加宏
#无条件宏控制
LOCAL_CFLAGS += -DBSP_PLATFORM=1
条件控制
#有条件的添加宏控制
BOARD_PROPERTY_OVERRIDES_SPLIT_ENABLED := true
ifeq ($(BOARD_PROPERTY_OVERRIDES_SPLIT_ENABLED), true)
property_overrides_split_enabled := true
endif
ifeq ($(PLATFORM_VERSION),11)
LOCAL_CFLAGS += -DPLATFORM_VERSION_11
endif
Android.bp
Android.bp 只是纯粹的配置文件,不包括分支、循环等流程控制。如果想要在Android.bp 中添加宏控,也是可以做到的。
直接添加宏
Android.bp直接添加宏,只需要在cflags后面添加对应的宏就OK了。
pwirisservice_defaults {
name: "pwirisservice_defaults",
cflags: [
"-DPXLW_IRIS",
],
}
之后再代码里就可以
#if defined(PXLW_IRIS)
#elif defined(PXLW_IRIS2P)
#else
#endif
arch
以下可以实现针对不同cpu架构走不同的逻辑,如果需要更加复杂的逻辑在bp里面就无法实现,bp的设计初衷也是如此,不过如果确实有特殊需求,可以通过编写Soong插件来实现:
cc_library {
...
srcs: ["generic.cpp"],
arch: {
arm: {
srcs: ["arm.cpp"],
},
x86: {
srcs: ["x86.cpp"],
},
},
}
条件控制
在Android.bp中,有条件的添加宏是不能直接做到的,必须借助go脚本实现动态控制编译项。
Android.bp在被编译系统读取的时候,在关键位置会发出一些callback给编译插件,并提供用于和编译系统交互的一系列api。
Android.bp
bootstrap_go_package
首先在Android.bp中添加 bootstrap_go_package
模块:
// 1. bootstrap_go_package
// 告诉编译系统当前模块是一个go语言的包
bootstrap_go_package {
name: "当前模块的包名",
pkgPath: "当前的包路径",
// 2. deps指定依赖的包名,
// 依赖可以在本地源码中,也可以在GitHub上
// 类似Android App build.gradle文件中的implementation关键字
deps: [
"外部依赖"
],
srcs: [
"插件源文件",
],
// 3. pluginFor: ["soong_build"],
// 告诉编译系统中这个包用来给Soong当编译插件使用,
// 所以当此Android.bp文件被读取时,先编译/启动该go脚本,执行srcs指定的源文件
pluginFor: ["soong_build"],
}
// 例如 vendor/pixelworks/libirisservice/android_r.bp
bootstrap_go_package {
name: "soong-vendor-pixelworks-libirisservice",
pkgPath: "android/soong/vendor/pixelworks/libirisservice",
deps: [
"soong-android",
"soong-cc",
],
srcs: [
"libirisservice.go",
],
pluginFor: ["soong_build"],
}
被关联的模块
该模块可以被其它模块通过指定 default
属性,从而继承。
// Android.bp 的 pwirisservice_defaults 模块,会被go注册关联
// Use cc_defaults when libirisservice.go is not used.
pwirisservice_defaults {
name: "pwirisservice_defaults",
header_libs: [
"pwirissoft_headers",
],
static_libs: [
"libjsoncpp",
"libtinyxml2",
],
shared_libs: [
"liblog",
// ...
"libpwirisfeature",
],
cflags: [
"-DPXLW_IRIS",
],
}
go脚本
在Android.bp同级目录添加go脚本文件:
package
// Android.bp中
bootstrap_go_package {
name: "soong-vendor-pixelworks-libirisservice",
pkgPath: "android/soong/vendor/pixelworks/libirisservice",
// ..
}
// go中
package vendor_pixelworks_libirisservice
init函数
init函数
:将回调函数注册到module中
func init() {
android.RegisterModuleType(“hello_defaults”, on_hello_defaults)
}
-
相当于插件的main函数,编译系统首先会回调此函数,
-
调用
RegisterModuleType
,将go中的回调函数注册到 Android.bp中的 module在例子中:
-
Android.bp的模块:
hello_defaults
模块 -
注册到module中的go回调函数:
on_hello_defaults()
函数on_hello_defaults
这个回调函数的名字最好是自定义的,防止对其他已有的module type造成影响。
-
回调函数
实现回调函数 on_hello_defaults函数
:注册 hook函数
,
func on_hello_defaults() android.Module {
module := cc.DefaultsFactory() // 获取module
android.AddLoadHook(module, hello_defaults_hook) // 注册hook
return module
}
-
执行时机
当 Android.bp 中的模块被解析时,回调函数
on_hello_defaults
被执行。(换句话说,获取go中module,获取hook的执行时机是在module被解析之后)
- 在这个回调中,可以读写编译当前模块所需要的所有参数,以及环境变量,
- 因此要更改编译逻辑,就在这个hook里面实现
-
在回调中,
-
获取 module
module := cc.DefaultsFactory()
要注意的是其中
cc.DefaultsFactory
要根据模块类型的不同而不同-
cc_binary
对应cc.DefaultsFactory
-
``cc_library_shared
对应
cc.LibrarySharedFactory()` -
java_library
对应java.LibraryFactory()
-
-
为module注册一个hook,
hello_defaults_hook
-
hook函数
调用hook函数hello_defaults_hook函数
:修改 module
基本套路:
-
通过ctx提供的接口,读取环境变量,
-
通过ctx设置一个结构体到编译系统,
-
该结构体的结构会对应于Android.bp中module的结构,
-
编译系统通过反射读取这个结构体,和Android.bp中的描述对比,
对应的键值对
-
若不存在,则添加
-
若存在,则覆盖
-
-
ps: 可供查阅的go文件
-
工具:
- 可以从 ctx.AConfig() 中获取好多属性
build/soong/android/config.go 中 对 build/soong/android/module.go 中的 androidBaseContext interface的各种函数实现
- 其中有一项是获取宏值的,之后回调xxxdroidDefaults添加宏信息。
package xxxparser
import (
"android/soong/android"
"android/soong/cc"
)
// 1.会运行init函数,将回调函数 注册到Android.bp::module中,
func init() {
// resister a module "xxx_defaults"
android.RegisterModuleType("xxx_defaults", xxxDefaultsFactory)
}
// 2.实现回调函数,注册hook函数
func xxxDefaultsFactory() (android.Module) {
module := cc.DefaultsFactory()
android.AddLoadHook(module, xxxHook)
return module
}
// 3.实现hook函数
func xxxHook(ctx android.LoadHookContext) {
type props struct {
Cflags []string
}
p := &props{}
p.Cflags = globalDefaults(ctx)
ctx.AppendProperties(p)
}
// 4.根据条件,添加宏
func globalDefaults(ctx android.BaseContext) ([]string) {
var cppflags []string
if ctx.AConfig().Getenv("ANDROIDBP_FUN") == "YES" {
cppflags = append(cppflags,"-DXXX")
}
return cppflags
}
一个hook例子
func hello_defaults_hook(ctx android.LoadHookContext) {
type props struct {
Cflags []string
Clang *bool
Include_dirs []string
Shared_libs []string
Sanitize struct {
Address *bool
}
}
p := &props{}
target_variant := ctx.AConfig().Getenv("TARGET_BUILD_VARIANT")
factory_build := ctx.AConfig().Getenv("FACTORY_BUILD")
if factory_build == "1" {
fmt.Println(TAG + "Disable ASan:FACTORY_BUILD=" + factory_build)
return
}
switch target_variant {
case "userdebug":
fmt.Println(TAG + "Enable ASan.")
p.Cflags = append(p.Cflags, "-Wno-error")
p.Cflags = append(p.Cflags, "-fno-omit-frame-pointer")
p.Cflags = append(p.Cflags, "-fno-sanitize-address-use-after-scope")
p.Cflags = append(p.Cflags, "-O0")
p.Cflags = append(p.Cflags, "-Wno-frame-larger-than=")
p.Cflags = append(p.Cflags, "-DHAS_MILLET_H")
p.Shared_libs = append(p.Shared_libs, "libtest")
p.Include_dirs = append(p.Include_dirs, "frameworks/base/XXXX/include")
p.Clang = proptools.BoolPtr(true)
p.Sanitize.Address = proptools.BoolPtr(true)
ctx.AppendProperties(p)
default:
fmt.Println(TAG + "Disable ASan:TARGET_BUILD_VARIANT=" + target_variant)
}
}
go
属性to module
something more about go
定义一个结构体,例如这里的 props
。在其中定义若干需要补充的Android.bp
中的属性。
type props struct {
Cflags []string
Clang *bool
Include_dirs []string
Shared_libs []string
Sanitize struct {
Address *bool
}
}
实例化结构体
p := &props{}
期间可能需要使用到环境变量:
// 获得环境变量
ctx.AConfig().Getenv("ANDROIDBP_FUN")
// 环境变量是否存在
ctx.AConfig().IsEnvTrue("ENABLE_USER2ENG")
if ctx.AConfig().IsEnvTrue("ENABLE_USER2ENG") {...}
fmt.Println("ENABLE_USER2ENG:",
ctx.AConfig().IsEnvTrue("ENABLE_USER2ENG"))
对该结构体对象进行填充,
p.Cflags = append(p.Cflags, "-Wno-error")
p.Cflags = append(p.Cflags, "-fno-omit-frame-pointer",
"-O0",
"-DHAS_MILLET_H")
p.Shared_libs = append(p.Shared_libs, "libtest")
p.Include_dirs = append(p.Include_dirs, "frameworks/base/XXXX/include")
p.Clang = proptools.BoolPtr(true)
p.Sanitize.Address = proptools.BoolPtr(true)
设置结构体对象到Android.bp中的module
ctx.AppendProperties(p)
时间
PrependProperties
Android.bp 是否定义宏。怎么判断
#ifdef ALLOW_ADBD_DISABLE_VERITY
if (verity.disabled) {
retval = FS_MGR_SETUP_VERITY_DISABLED;
LINFO << "Attempt to cleanly disable verity - only works in USERDEBUG";
goto out;
}
#endif
demo
package vendor_pixelworks_libirisservice
import (
"android/soong/android"
"android/soong/cc"
"fmt"
"time"
"strings"
)
func init() {
// resister modules
android.RegisterModuleType("pwirisservice_defaults", pwirisserviceDefaultsFactory)
}
func pwirisserviceDefaultsFactory() android.Module {
module := cc.DefaultsFactory()
android.AddLoadHook(module, pwirisserviceHook)
return module
}
func pwirisserviceHook(ctx android.LoadHookContext) {
fmt.Println("BUILD_TAG = ", ctx.AConfig().Getenv("BUILD_TAG"))
// BUILD_TAG
buildTag := ctx.AConfig().Getenv("BUILD_TAG")
// buildTag := "jenkins-sm8350_r_dev_i7p_build-290"
fmt.Println("BUILD_TAG = ", buildTag)
var lib_version string
var is_dailybuild bool
if strings.Contains(buildTag, "jenkins") {
fmt.Println("Jenkins Daily build: BUILD_TAG = ", buildTag)
is_dailybuild = true
} else {
fmt.Println("Not Jenkins Daily build: BUILD_TAG = ", buildTag)
is_dailybuild = false
}
// time
formattedTime := time.Now().Format("060102")
// EXECUTOR_NUMBER
var executorNum string = ctx.AConfig().Getenv("EXECUTOR_NUMBER")
if len(executorNum) < 2 {
executorNum = "0" + executorNum
}
// Build server
if (is_dailybuild) {
lib_version += buildTag + "."
lib_version += formattedTime + "."
lib_version += executorNum
lib_version += " jenkins "
} else {
lib_version += "This version is not build on jenkins server, build time: " + formattedTime
}
fmt.Println("Final version : ", lib_version)
type props struct {
Cflags []string
}
p := &props{}
p.Cflags = append(p.Cflags, "-DVERSION_NO=\"IRIS_SERVICE_VER:"+ctx.AConfig().Getenv("BUILD_TAG")+"\"")
p.Cflags = append(p.Cflags, "-DVERSION_SDK_VERSION=\"IRIS_SDK_VERSION:"+lib_version+"\"")
ctx.AppendProperties(p)
}
.bp .mk对应关系
var rewriteProperties = map[string](func(variableAssignmentContext) error){
// custom functions
"LOCAL_32_BIT_ONLY": local32BitOnly,
"LOCAL_AIDL_INCLUDES": localAidlIncludes,
"LOCAL_ASSET_DIR": localizePathList("asset_dirs"),
"LOCAL_C_INCLUDES": localIncludeDirs,
"LOCAL_EXPORT_C_INCLUDE_DIRS": exportIncludeDirs,
"LOCAL_JARJAR_RULES": localizePath("jarjar_rules"),
"LOCAL_LDFLAGS": ldflags,
"LOCAL_MODULE_CLASS": prebuiltClass,
"LOCAL_MODULE_STEM": stem,
"LOCAL_MODULE_HOST_OS": hostOs,
"LOCAL_RESOURCE_DIR": localizePathList("resource_dirs"),
"LOCAL_SANITIZE": sanitize(""),
"LOCAL_SANITIZE_DIAG": sanitize("diag."),
"LOCAL_STRIP_MODULE": strip(),
"LOCAL_CFLAGS": cflags,
"LOCAL_UNINSTALLABLE_MODULE": invert("installable"),
"LOCAL_PROGUARD_ENABLED": proguardEnabled,
"LOCAL_MODULE_PATH": prebuiltModulePath,
// composite functions
"LOCAL_MODULE_TAGS": includeVariableIf(bpVariable{"tags", bpparser.ListType}, not(valueDumpEquals("optional"))),
// skip functions
"LOCAL_ADDITIONAL_DEPENDENCIES": skip, // TODO: check for only .mk files?
"LOCAL_CPP_EXTENSION": skip,
"LOCAL_MODULE_SUFFIX": skip, // TODO
"LOCAL_PATH": skip, // Nothing to do, except maybe avoid the "./" in paths?
"LOCAL_PRELINK_MODULE": skip, // Already phased out
"LOCAL_BUILT_MODULE_STEM": skip,
"LOCAL_USE_AAPT2": skip, // Always enabled in Soong
"LOCAL_JAR_EXCLUDE_FILES": skip, // Soong never excludes files from jars
"LOCAL_ANNOTATION_PROCESSOR_CLASSES": skip, // Soong gets the processor classes from the plugin
"LOCAL_CTS_TEST_PACKAGE": skip, // Obsolete
"LOCAL_JACK_ENABLED": skip, // Obselete
"LOCAL_JACK_FLAGS": skip, // Obselete
}
func init() {
addStandardProperties(bpparser.StringType,
map[string]string{
"LOCAL_MODULE": "name",
"LOCAL_CXX_STL": "stl",
"LOCAL_MULTILIB": "compile_multilib",
"LOCAL_ARM_MODE_HACK": "instruction_set",
"LOCAL_SDK_VERSION": "sdk_version",
"LOCAL_MIN_SDK_VERSION": "min_sdk_version",
"LOCAL_NDK_STL_VARIANT": "stl",
"LOCAL_JAR_MANIFEST": "manifest",
"LOCAL_CERTIFICATE": "certificate",
"LOCAL_PACKAGE_NAME": "name",
"LOCAL_MODULE_RELATIVE_PATH": "relative_install_path",
"LOCAL_PROTOC_OPTIMIZE_TYPE": "proto.type",
"LOCAL_MODULE_OWNER": "owner",
"LOCAL_RENDERSCRIPT_TARGET_API": "renderscript.target_api",
"LOCAL_NOTICE_FILE": "notice",
"LOCAL_JAVA_LANGUAGE_VERSION": "java_version",
"LOCAL_INSTRUMENTATION_FOR": "instrumentation_for",
"LOCAL_MANIFEST_FILE": "manifest",
"LOCAL_DEX_PREOPT_PROFILE_CLASS_LISTING": "dex_preopt.profile",
"LOCAL_TEST_CONFIG": "test_config",
})
addStandardProperties(bpparser.ListType,
map[string]string{
"LOCAL_SRC_FILES": "srcs",
"LOCAL_SRC_FILES_EXCLUDE": "exclude_srcs",
"LOCAL_HEADER_LIBRARIES": "header_libs",
"LOCAL_SHARED_LIBRARIES": "shared_libs",
"LOCAL_STATIC_LIBRARIES": "static_libs",
"LOCAL_WHOLE_STATIC_LIBRARIES": "whole_static_libs",
"LOCAL_SYSTEM_SHARED_LIBRARIES": "system_shared_libs",
"LOCAL_ASFLAGS": "asflags",
"LOCAL_CLANG_ASFLAGS": "clang_asflags",
"LOCAL_CONLYFLAGS": "conlyflags",
"LOCAL_CPPFLAGS": "cppflags",
"LOCAL_REQUIRED_MODULES": "required",
"LOCAL_OVERRIDES_MODULES": "overrides",
"LOCAL_LDLIBS": "host_ldlibs",
"LOCAL_CLANG_CFLAGS": "clang_cflags",
"LOCAL_YACCFLAGS": "yaccflags",
"LOCAL_SANITIZE_RECOVER": "sanitize.recover",
"LOCAL_LOGTAGS_FILES": "logtags",
"LOCAL_EXPORT_HEADER_LIBRARY_HEADERS": "export_header_lib_headers",
"LOCAL_EXPORT_SHARED_LIBRARY_HEADERS": "export_shared_lib_headers",
"LOCAL_EXPORT_STATIC_LIBRARY_HEADERS": "export_static_lib_headers",
"LOCAL_INIT_RC": "init_rc",
"LOCAL_VINTF_FRAGMENTS": "vintf_fragments",
"LOCAL_TIDY_FLAGS": "tidy_flags",
// TODO: This is comma-separated, not space-separated
"LOCAL_TIDY_CHECKS": "tidy_checks",
"LOCAL_RENDERSCRIPT_INCLUDES": "renderscript.include_dirs",
"LOCAL_RENDERSCRIPT_FLAGS": "renderscript.flags",
"LOCAL_JAVA_RESOURCE_DIRS": "java_resource_dirs",
"LOCAL_JAVACFLAGS": "javacflags",
"LOCAL_ERROR_PRONE_FLAGS": "errorprone.javacflags",
"LOCAL_DX_FLAGS": "dxflags",
"LOCAL_JAVA_LIBRARIES": "libs",
"LOCAL_STATIC_JAVA_LIBRARIES": "static_libs",
"LOCAL_JNI_SHARED_LIBRARIES": "jni_libs",
"LOCAL_AAPT_FLAGS": "aaptflags",
"LOCAL_PACKAGE_SPLITS": "package_splits",
"LOCAL_COMPATIBILITY_SUITE": "test_suites",
"LOCAL_OVERRIDES_PACKAGES": "overrides",
"LOCAL_ANNOTATION_PROCESSORS": "plugins",
"LOCAL_PROGUARD_FLAGS": "optimize.proguard_flags",
"LOCAL_PROGUARD_FLAG_FILES": "optimize.proguard_flags_files",
// These will be rewritten to libs/static_libs by bpfix, after their presence is used to convert
// java_library_static to android_library.
"LOCAL_SHARED_ANDROID_LIBRARIES": "android_libs",
"LOCAL_STATIC_ANDROID_LIBRARIES": "android_static_libs",
"LOCAL_ADDITIONAL_CERTIFICATES": "additional_certificates",
// Jacoco filters:
"LOCAL_JACK_COVERAGE_INCLUDE_FILTER": "jacoco.include_filter",
"LOCAL_JACK_COVERAGE_EXCLUDE_FILTER": "jacoco.exclude_filter",
})
addStandardProperties(bpparser.BoolType,
map[string]string{
// Bool properties
"LOCAL_IS_HOST_MODULE": "host",
"LOCAL_CLANG": "clang",
"LOCAL_FORCE_STATIC_EXECUTABLE": "static_executable",
"LOCAL_NATIVE_COVERAGE": "native_coverage",
"LOCAL_NO_CRT": "nocrt",
"LOCAL_ALLOW_UNDEFINED_SYMBOLS": "allow_undefined_symbols",
"LOCAL_RTTI_FLAG": "rtti",
"LOCAL_NO_STANDARD_LIBRARIES": "no_standard_libs",
"LOCAL_PACK_MODULE_RELOCATIONS": "pack_relocations",
"LOCAL_TIDY": "tidy",
"LOCAL_USE_CLANG_LLD": "use_clang_lld",
"LOCAL_PROPRIETARY_MODULE": "proprietary",
"LOCAL_VENDOR_MODULE": "vendor",
"LOCAL_ODM_MODULE": "device_specific",
"LOCAL_PRODUCT_MODULE": "product_specific",
"LOCAL_PRODUCT_SERVICES_MODULE": "product_services_specific",
"LOCAL_EXPORT_PACKAGE_RESOURCES": "export_package_resources",
"LOCAL_PRIVILEGED_MODULE": "privileged",
"LOCAL_AAPT_INCLUDE_ALL_RESOURCES": "aapt_include_all_resources",
"LOCAL_USE_EMBEDDED_NATIVE_LIBS": "use_embedded_native_libs",
"LOCAL_USE_EMBEDDED_DEX": "use_embedded_dex",
"LOCAL_DEX_PREOPT": "dex_preopt.enabled",
"LOCAL_DEX_PREOPT_CAPP_IMAGE": "dex_preopt.app_image",
"LOCAL_DEX_PREOPT_GENERATE_PROFILE": "dex_preopt.profile_guided",
"LOCAL_PRIVATE_PLATFORM_APIS": "platform_apis",
"LOCAL_JETIFIER_ENABLED": "jetifier",
})
}
var moduleTypes = map[string]string{
"BUILD_SHARED_LIBRARY": "cc_library_shared",
"BUILD_STATIC_LIBRARY": "cc_library_static",
"BUILD_HOST_SHARED_LIBRARY": "cc_library_host_shared",
"BUILD_HOST_STATIC_LIBRARY": "cc_library_host_static",
"BUILD_HEADER_LIBRARY": "cc_library_headers",
"BUILD_EXECUTABLE": "cc_binary",
"BUILD_HOST_EXECUTABLE": "cc_binary_host",
"BUILD_NATIVE_TEST": "cc_test",
"BUILD_HOST_NATIVE_TEST": "cc_test_host",
"BUILD_NATIVE_BENCHMARK": "cc_benchmark",
"BUILD_HOST_NATIVE_BENCHMARK": "cc_benchmark_host",
"BUILD_JAVA_LIBRARY": "java_library_installable", // will be rewritten to java_library by bpfix
"BUILD_STATIC_JAVA_LIBRARY": "java_library",
"BUILD_HOST_JAVA_LIBRARY": "java_library_host",
"BUILD_HOST_DALVIK_JAVA_LIBRARY": "java_library_host_dalvik",
"BUILD_PACKAGE": "android_app",
"BUILD_CTS_EXECUTABLE": "cc_binary", // will be further massaged by bpfix depending on the output path
"BUILD_CTS_SUPPORT_PACKAGE": "cts_support_package", // will be rewritten to android_test by bpfix
"BUILD_CTS_PACKAGE": "cts_package", // will be rewritten to android_test by bpfix
"BUILD_CTS_TARGET_JAVA_LIBRARY": "cts_target_java_library", // will be rewritten to java_library by bpfix
"BUILD_CTS_HOST_JAVA_LIBRARY": "cts_host_java_library", // will be rewritten to java_library_host by bpfix
}
var prebuiltTypes = map[string]string{
"SHARED_LIBRARIES": "cc_prebuilt_library_shared",
"STATIC_LIBRARIES": "cc_prebuilt_library_static",
"EXECUTABLES": "cc_prebuilt_binary",
"JAVA_LIBRARIES": "java_import",
"ETC": "prebuilt_etc",
}
gn ninja
gn
介绍
gn 意思是 generate ninja,即生成 Ninja 所需的文件(meta data),所以 gn 自称为元数据构建(meta-build)系统,也是 google chromium 团队出品,gn 的文件后缀为 .gn
、.gni
。
.gn是源文件;.gni是头文件,类似C++中的头文件.h 通过import进行引用
import("//build/config/c++/c++.gni")
Chromium是用gn和ninja进行编译的,即gn把.gn文件转换成.ninja文件,然后ninja根据.ninja文件将源码生成目标程序。gn和ninja的关系就与cmake和make的关系差不多。
ninja是直接接触到工程文件的部分,gn是用来描述并生成ninja文件的方式。以命令行为驱动,调用gn,执行ninja。
- 命令行主要做解析方面的工作,解析待编译的产品名称,加载相关配置(完成“衣服”的材质选择);
- 调用gn是为了根据命令行解析的产品名称和编译类型,配置编译工具链和全局的编译选项(准备“衣服”的设计图纸);
- 最后执行Ninja,以启动编译并生成对应的产品版本(“穿针引线”,完成衣服的编织)。
类型与变量
GN使用非常简单的动态类型语言。类型是:
- 布尔(
true
,false
)。 - 64位有符号整数。
- 字符串。
- 列表(任何其他类型)。
- 范围/作用域(Scopes)(有点像字典,仅是内置的东西(built-in stuff))。
用户自定义变量之外,gn 还内建了 20+ 个变量,比如:
- current_cpu、current_os、current_toolchain
- target_cpu、target_os、target_name、target_out_dir
- gn_version
- python_path
字符串
字符串用双引号括起来,并使用反斜杠作为转义字符。唯一支持的转义序列是:
\"
(用于直接引用)\$
(字面上的美元符号)\\
(用于文字反斜杠)
任何其他反斜杠的使用都被视为文字反斜杠。
使用$
支持简单的变量替换,可以选择{}
包围名称。
a = "mypath"
b = "$a/foo.cc" # b -> "mypath/foo.cc"
c = "foo${a}bar.cc" # c -> "foomypathbar.cc"
可以使用 $0xFF
语法对8位字符进行编码,
列表/清单
追加 +
a = [ "first" ]
a += [ "second" ] # [ "first", "second" ]
a += [ "third", "fourth" ] # [ "first", "second", "third", "fourth" ]
b = a + [ "fifth" ] # [ "first", "second", "third", "fourth", "fifth" ]
将列表追加到另一个列表,是追加第二个列表中的项目,而不是将列表追加为嵌套成员。
删除 -
a = [ "first", "second", "third", "first" ]
b = a - [ "first" ] # [ "second", "third" ]
a -= [ "second" ] # [ "first", "third", "fourth" ]
c = [ "second" ]
b = b - c # [ "third" ]
提取 [0]
列表支持从零开始的下标以提取值:
a = [ "first", "second", "third" ]
b = a[1] # -> "second"
[]运算符是只读的,不能用来改变列表。这个主要的用例是当一个外部脚本返回几个已知的值。
赋值 =
a = [“one”]
a = [“two”]#错误:用非空列表覆盖非空列表。
a = []#OK
a = [“two”]#OK
请注意,构建脚本的执行没有内在知识的底层数据的意义。例如,这意味着它不知道sources
是一个文件名列表。所以,如果你删除一个项目,它必须匹配文字字符串,而不是指定一个不同的名称,那将解析为相同的文件名称。
条件/循环
if(is_linux ||(is_win && target_cpu ==“x86”)){
sources -= [ "something.cc" ]
} else if(...){
...
} else {
...
}
可以使用foreach
迭代一个列表。
foreach(i,mylist){
print(i) # Note: i is a copy of each element, not a reference to it.
}
变量
board_arch = "arm" # 定义变量
if (board_arch != "") { # 使用变量
cflags += [ "-march=$board_arch" ] # 字符串中使用变量
cflags_cc += [ "-march=${board_arch}" ] # 加上 {} 是等效的
}
gn
变量可以查看 gn help
Built-in predefined variables 章节。
current_cpu
: [string]
current_os
: [string]
current_toolchain
: [string]
default_toolchain
: [string]
gn_version
: [number]
host_cpu
: [string]
host_os
: [string]
invoker
: [string]
python_path
: [string]
root_build_dir
: [string]
root_gen_dir
: [string]
root_out_dir
: [string]
target_cpu
: [string]
target_gen_dir
: [string]
target_name
: [string]
target_os
: [string]
target_out_dir
: [string]
文件/目录
文件和目录名称是字符串,被解释为 相对于当前构建文件的目录
。有三种可能的形式:
相对名称:
"foo.cc"
"src/foo.cc"
"../src/foo.cc"
源代码树绝对名称:
“//net/foo.cc”
“//base/test/foo.cc”
系统绝对名称(罕见,通常用于包含目录):
"/usr/local/include/"
"/C:/Program Files/Windows Kits/Include"
路径
你可以以相对于当前目录的方式指定路径。标准上说,要引用非本文件中标识时,除了它要在不同上下文运行,我们建议使用绝对路径。
标识
标识是有格式要求的字符串,最终形成的依赖关系图中所有的元素(目标 Target、配置、工具链)都由标识做唯一识别。
它格式要求是"//<path>[:<name>][(<toolchain>)]"
组成:
除了 path 不能省略外,其他都能省,如果 name 省略了则标识与 path 最后一个字段同名的那么,举例:
"//base/test:test_support(//build/toolchain/win:msvc)"
最完整格式,定位到root/base/test/BUILD.gn
文件中的test_support
"//base/test:test_support"
"//base/test"
等价与"//base/test:test"
完整的标识
"//base/test:test_support(//build/toolchain/win:msvc)"
完整的标识还包括处理该标识要使用的工具链。工具链通常是以继承的方式被默认指定,当然你也可以显示指定。
上面这个标识会去//build/toolchain/win
文件查到名叫msvc
的工具链定义,那个定义会知道如何处理test_support
这个标识。
如果你指向的标识就在此个build文件,你可以省略路径,而只是从冒号开始。
“:base”
"//base/test:test_support"
上面这个标识指示到/base/test/BUILD.gn
中查找名称是"test_support"
的标识
当加载构建文件时,如果在相对于source root
给定的路径不存在时,GN
将查找build/secondary
中的辅助树。
"//base/test"
书写时,可以省略标识的冒号和名称,这时名称默认使用目录的最后一段。
(以下的“=”表示等同)
"//net"
等价于 "//net:net"
"//tools/gn"
等价于 "//tools/gn:gn"
函数
内置函数
gn 支持简单的控制语句,如:if…else、foreach 等,gn 也支持函数,并且内建了很多函数。
gn 有 30+ 个内建函数,包括:
import
:引入一个文件,但与 c/c++ 的 include 不同,import 的文件将独立执行并将执行结果放入当前文件。
getenv
:获取环境变量
print
:不解释
read_file
、write_file
foreach
:迭代一个 list
config
:定义 configuration 对象
set_defaults
:定义某个 target 的成员变量默认值
template
:定义一套 rule,调用 rule 能够生成一个 target
print assert static_library
print("hello, world")
# 如果is_win为真,就打印后面的内容
assert(is_win, "This should only be executed on Windows")
# 一些函数在它们下面接受一个由{ }组成的代码块:
# static_library内部会使用sources这个内置变量
static_library("mylibrary") {
sources = [ "a.cc" ]
}
print
,assert
,static_library
都是库函数,或者叫内置函数.static_library
的调用有点奇怪,它是个函数,函数内部会使用sources
这个内置变量,sources = [ "a.cc" ]
相当于先传参给这个函数的内置变量,并调用这个函数。学习ng得习惯这种语法方式,被大量的使用.
文件和函数调用后面跟着{ }
块引入新的作用域。作用域是嵌套的。当您读取一个变量时,将会以相反的顺序搜索包含的作用域,直到找到匹配的名称。变量写入总是进入最内层的作用域。
除了最内层的作用域以外,没有办法修改任何封闭作用域。这意味着当你定义一个目标时,例如,你在块内部做的任何事情都不会泄露到文件的其余部分。
if
/ else
/ foreach
语句,即使他们使用{ }
,不会引入新的范围,所以更改将持续在语句之外。
配置项 | configs
configs
如果希望定义一些配置数据(并且有嵌套),然后赋值给某个变量,可以使用config。
记录完成目标项所需的配置信息。指定标志集,包含目录和定义的命名对象。 sets of compiler flags, includes, defines
目标可以根据需要添加或删除。所以在实践中,通常会用configs += [ ":myconfig" ]
追加默认列表。
构建配置文件buildconfig
通常指定设置默认配置列表的目标默认值。
# build/config/BUILD.gn
# 创建一个标签为 cpu_arch 的配置项
config("cpu_arch") {
cflags = [ "-march=$board_arch" ] # 用 config 函数定义一个名为 cpu_arch 的配置对象
}
# 创建一个标签为 ohos 的配置项 —— 使用标签为 myconfig 的配置项
config("ohos") { # 定义一个名为 ohos 的配置对象
configs = [
":cpu_arch", # 包含上面 cpu_arch 的配置对象
":stack_protector",
]
include_dirs = [ "include/common" ]
defines = [ "ENABLE_DOOM_MELON" ]
}
# 生成可执行文件
executable("mything") {
# 使用标签为 myconfig 的配置项来生成目标文件
configs = [ ":myconfig" ]
}
然后就可以使用标识将配置对象config对象
ohos
,赋值给变量default_target_configs
default_target_configs = [ "//build/config:ohos" ]
默认目标set_defaults
为目标类型设置一些默认值——通常在构建配置文件.gn
中完成。
设置一个默认配置列表,它定义每个目标类型的构建标志和其他设置信息。
例如,声明static_library
时,将应用静态库的目标默认值。 这些值可以由目标覆盖,修改或保留。
# This call is typically in the build config file (see above).
# 在chromium中是//build/config/BUILDCONFIG.gn
set_defaults("static_library") {
configs = [ "//build:rtti_setup", "//build:extra_warnings" ]
}
# This would be in your directory's BUILD.gn file.
static_library("mylib") {
# At this point configs is set to [ "//build:rtti_setup", "//build:extra_warnings" ]
# by default but may be modified.
configs -= "//build:extra_warnings" # Don't want these warnings.
configs += ":mylib_config" # Add some more configs.
}
设置目标默认值的其他用例—— 通过模板定义自己的目标类型,并想要指定某些默认值。
公共配置shared_library
目标target 可以将设置应用于依赖它的其他目标。
config("my_external_library_config") {
includes = "."
defines = [ "DISABLE_JANK" ]
}
然后这个配置作为“公共”配置被添加到目标。它既适用于目标,也适用于直接依赖目标的目标。
shared_library("my_external_library") {
...
# Targets that depend on this get this config applied.
public_configs = [ ":my_external_library_config" ]
}
依赖目标又可以通过将目标作为“公共”依赖项添加到另一个级别,从而将依赖关系树转发到另一个级别。
static_library("intermediate_library") {
...
# Targets that depend on this one also get the configs from "my external library".
public_deps = [ ":my_external_library" ]
}
通过把目标设置成all_dependent_config
,目标可以将配置转发到所有依赖项,直到达到一个链接边界为止。但建议不要这样做,因为它将比必要的构建配置超出更多的标志和定义。
使用public_deps来控制哪些标志适用于哪里来代替它。
在Chrome中,更喜欢使用构建标志头系统build/buildflag_header.gni
用于定义的构建标题头文件系统,来防止编译器定义导致的大多数编译错误。
目标项/功能项 | targets
target 是构造表中的一个节点,它含有一些变量,以完成一些操作,通常代表将要生成的某种类型的可执行文件或库文件。
变量就像是操作的配置数据
target 就像是一段封装好的操作模块。
target 的写法是:
<target>("<name>") {
<var> = ...
}
有哪些
每个 targte 都有自己的用法,gn help <target>
查看,里面都会有一段 demo 代码可以拿来直接使用。
小小的分一下类
执行某个或某组动作
- action:单次运行的动作——运行一个脚本来生成一个文件。
- action_foreach:在多个文件中依次运行脚本的 target —— 循环为每个源文件运行脚本,依次产生文件。
- copy:执行 copy 动作
生成最终目标 —— 下面这 4 个非常必要,必须选择一个或多个进行定义
- excutable:指定 target 是个可执行文件
- source_set:定义源码集,会逐一对应生成 .o 文件,即尚未链接(link)的文件
- shared_library:声明一个动态(win=.dll、linux=.so)
- static_library:声明一个静态库(win=.lib、linux=.a)
辅助类
- group:声明一个 target —— 生产依赖关系组。引用一个或多个其他目标的虚拟依赖关系节点。包含一个或多个目标的虚拟节点(目标)。
内置的target类型
内置的target类型(参考gn help
):
action
:运行一个脚本来生成一个文件。executable
:生成一个可执行文件。group
:生产依赖关系组。引用一个或多个其他目标的虚拟依赖关系节点。包含一个或多个目标的虚拟节点(目标)。static_library
:生成.lib或.a 静态链接库shared_library
:生成.dll或.so动态链接库action_foreach
:循环为每个源文件运行脚本,依次产生文件。bundle_data
:产生要加入Mac/iOS包的数据。create_bundle
:创建一个Mac / iOS包。loadable_module
:一个只用于运行时的.dll或.so。source_set
:一个轻量的虚拟静态库(通常指向一个真实静态库)(通常比真正的静态库更可取,因为它的构建速度会更快)。
可以使用模板(templates)来扩展可使用的目标。
Chrome中的常用模板:
component
:取决于构建类型,是 源文件集合 或 共享库。test
:可执行测试。在移动平台,它用于创建测试原生app。app
:可执行文件或Mac / iOS应用程序。android_apk
:生成一个APK。有许多Android应用,参考//build/config/android/rules.gni
。
常用target
gn help <target>
查看
copy
copy target 可以根据 sources 和 outputs 变量实现文件拷贝:
copy("mydll") {
sources = [ "mydll.dll" ]
outputs = [ "$target_out_dir/mydll.dll" ]
}
copy("compiler") {
sources = [
"//prebuilts/gcc/linux-x86/arm/arm-linux-ohoseabi-gcc/arm-linux-musleabi",
"//prebuilts/gcc/linux-x86/arm/arm-linux-ohoseabi-gcc/arm-linux-ohoseabi",
]
outputs = [ "$ndk_linux_toolchains_out_dir/{{source_file_part}}" ]
}
action
action target
单次运行的动作——运行一个脚本来生成一个文件, 可以完成 script 变量指定的脚本,
Harmony 中 build/lite/BUILD.gn
中生成根文件系统的操作,使用了 action target。
action("gen_rootfs") {
deps = [ ":ohos" ]
script = "//build/lite/gen_rootfs.py" # 执行此 python 文件
outputs = [ "$target_gen_dir/gen_rootfs.log" ] # 输出 log 文件
out_dir = rebase_path("$root_out_dir")
# python 文件可以接受的命令行参数
args = [
"--path=$out_dir",
"--kernel=$ohos_kernel_type",
"--storage=$storage_type",
"--strip_command=$ohos_current_strip_command",
"--dmverity=$enable_ohos_security_dmverity",
]
}
模板
gn
提供了很多内置函数。如果想自己定义函数——模板,模板就是自定义函数。
通常情况下模板定义在一个.gni 文件中, 用户 import 该文件看到模板的定义。
# 定义模板, 文件路径: //tools/idl_compiler.gni,
# 后缀.gni 代表这是一个 gn import file
template("idl") { #自定义一个名称为 "idl"的函数
#调用内置函数 source_set
source_set(target_name) {
#invoker为内置变量,含义为调用者内容 即:[ "a", "b" ]的内容
sources = invoker.sources
}
}
# 如何使用模板, 用import,类似 C语言的 #include
import("//tools/idl_compiler.gni")
# 等同于调用 idl
idl("my_interfaces") {
#给idl传参, 参数的接收方是 invoker.sources
sources = [ "a", "b" ]
}
# Declares a script that compiles IDL files to source, and then compiles those
#source files.
template("idl") {
# Always base helper targets on target_name so they're unique。Target name
# will be the string passed as the name when the template is invoked.
idl_target_name =“$ {target_name} _generate”
action_foreach(idl_target_name){
...
}
# Your template should always define a target with the name target_name.
# When other targets depend on your template invocation, this will be the
# destination of that dependency.
source_set(target_name){
...
deps = [ ":$idl_target_name" ] # Require the sources to be compiled.
}
}
声明模板会在当时在范围内的变量周围创建一个闭包,模板通常会将感兴趣的值复制到自己的范围中。
当模板被调用时,魔术变量invoker
被用来从调用范围中读取变量。
template("idl") {
source_set(target_name){
sources = invoker.sources
}
}
模板执行时的当前目录,是调用的构建文件.gn
的目录,而不是模板源文件的目录。
Ninja
https://blog.csdn.net/MAX__YF/article/details/125856490
https://blog.csdn.net/tongyi04/article/details/110131799
ninja是一个专注于速度的小型构件系统。
- 专注于速度,
- 不支持分支、循环等流程控制,也不支持逻辑运算,
- 但允许以其它语言如来维护这些复杂的编译流程和逻辑
- 只需拷贝一个可执行程序ninja就可以执行,不需要依赖任何库。
ninja 像是编译器(Compiler)的预处理器,主要目的是递归查找好依赖关系,提前建立依赖树,gcc 可按照依赖树依次编译,大大减少编译期间杂乱的编译顺序造成的查找和重复时间。
基本概念
概念
-
edge(边) build语句:
可以指定目标(target)输出(output)、规则(rule)与输入(input)
指定目标(输出)、规则与输入,是编译过程拓扑图中的一条边(edge)。
-
target(目标)/output(输出)):
build语句的前半段,编译过程需要产生的目标,由build语句指定。
-
input(输入):
build语句的后半段,用于产生output的文件或目标,另一种称呼是依赖
-
rule(规则):
通过指定command与一些内置变量,决定如何从输入产生输出
-
pool(池):
一组rule或edge,通过指定其depth,可以控制并行上限。
-
scope(作用域):
变量的作用范围,有rule与build语句的块级,也有文件级别。rule也有scope。
关键字
-
build
: 定义一个edge -
rule
: 定义一个rule -
pool
: 定义一个pool -
default
: 指定默认的一个或多个target -
include
: 添加一个ninja文件到当前scope -
subninja
: 添加一个ninja文件,其scope与当前文件不同。 -
phony
: 一个内置的特殊规则,指定非文件的target
变量
变量分两种,内置变量与自定义变量。
通过var = str
的方式定义,通过$var
或${var}
的方式引用。变量类型只有字符串这一种。
内置变量
内置变量只有两个:
builddir
: 指定一些文件的输出目录,如.ninja_log
、.ninja_deps
等。
ninja_required_version
: 指定ninja命令的最低版本
自定义变量
一个rule就是通过${in}
输入的目标列表,生成${out}
的输出目标列表。
目标一般都是文件。有一个内置的特殊规则phony,可以指定非文件目标。
rule中变量
除了可以自定义变量以外,包括command在内,rule还有以下变量:
command
:定义一个规则必备的变量,指定实际执行的命令。description
:command的说明,会替代command在无-v时打印generator
:指定后,这条rule生成的文件不会被默认清理in
:空格分割的input列表in_newline
:换行符分割的input列表。out
:空格分割的output列表depfile
:指定一个Makefile文件作为额外的显式依赖。deps
:指定gcc或mscv方式的依赖处理msvc_deps_prefile
:在deps = msvc情况下,指定需要去除的msvc输出前缀。restat
:在command执行结束后,如果output时间戳不变,则当作未执行rspfile,rspfile_content
:同时指定,在执行command前,把rspfile_content写入rspfile文件,执行成功后删除。
语法
示例
定义规则cc
cflags = -Wall -Werror #定义一个全局变量,用于给规则传参.
rule cc # 定义一个叫cc的规则,cc是rule名
# command定义一个规则 必 备 的变量,指定实际执行的命令。
command = gcc $cflags -c $in -o $out # 这个rule将生成的bash命令,接收外部三个参数
var = str # 局部变量,没有用到仅做展示
第一个build,
build foo.o: cc foo.c
-
最终编译选项∶
gcc -Wall -Werror -c foo.c -o foo.o
-
将
foo.c
用cc规则编译成foo.o
第二个build,cflags
是局部变量,范围只在编译special.c
上有效
build special.o: cc special.c
#在build块中,也可以对rule块的变量进行扩展(复写)
cflags = -Wall #局部变量,范围只在编译special.c上有效
-
最终编译选项∶
gcc -Wall -c foo.c -o foo.o
-
将
special.c
用cc规则编译成special.o
# 提供取别名的功能,使用phony规则
build ability: phony ./libability.so
build edge
在build块中,也可以对rule块的变量进行扩展(复写)。build的复杂情况举例,代码如下:
Gcc = gcc # 全局变量
# rule
rule name # name是rule名
command = ${Gcc} ${in} > ${out} # 执行命令
var = str # 局部变量
# rule_name 规则名称
# 在build块中,也可以对rule块的变量进行扩展(复写)
build output0 output1 | output2 output3: rule_name $
input0 input1 $ # 显示依赖
| input2 input3 $ # 隐式依赖
|| input4 input5 # order-only依赖 可有可无
var0 = str0
var1 = str1
行末的$是转义字符,并未真正换行;
-
output0
和output1
是显示(explicit)输出,会出现在${out}
列表中;
|
后的output2
和output3
是隐式(implicit)输出,不会出现在${out}
列表中; -
rule_name
是规则名称; -
input0
和input1
是显示依赖(输入),会出现在${in}
列表中;
|
后的input2
和input3
是隐式依赖,不会出现在${in}
列表中;
||
后的input4
和input5
是隐式order-only依赖,不会出现在${in}
列表中。
pool
pool的意义,在于限制一些非常消耗硬件资源的edge同时执行。
目前,ninja只有一个内置的pool,名为console。
-
console,这个pool的depth等于1,只能同时执行1个edge。
-
可以直接访问stdin、stdout、stderr三个特殊stream。
pool example
depth = 2
rule echo_var
command = echo ${var} >> ${out}
pool = example
build a : echo_var
var = a
build b : echo_var
var = b
build c : echo_var
var = c
示例
//test.h
#include<stdio.h>
void print_ninja()
{
printf("this is a ninja_test!\n");
}
//test.c
#include"test.h"
int main(int argc, char *argv[])
{
print_ninja();
return 0;
}
build.ninja
ninja_required_version = 1.5
GCC = gcc
cflags = -Wall
rule rule1
command = $GCC -c $cflags -MD -MF $out.d $in -o $out
description = 编译 $in 成为 $out
depfile = $out.d
deps = gcc
build test.o : rule1 test.c
rule link
command = $GCC $DEFINES $INCLUDES $cflags $in -o $out
description = 链接 $in 成为 $out
build test : link test.o
# 编译all,就是做任务build test
build all: phony test
# 默认编译什么(单独运行ninja)
default all
ninja
gn gen生成
使用gn gen out/xx
生成了以下文件。
args.gn build.ninja build.ninja.d NOTICE_FILE obj test_info toolchain.ninja
args.gn
∶一些参数build.ninja
∶ ninja的主文件。重新生成一遍*ninja
文件build.ninja.d
∶记录生成所有.ninja 所依赖的BUILD.gn
文件路劲列表,一个BUILD.gn
就生成
一个.ninja
文件
-
obj
∶各组件模块构建/编译文件输出地. -
toolchain
∶放置ninja
规则,将被subninja
进build.ninja
。
build.ninja
前面部分是定义一个 gn
规则 —— 重新生成一遍*ninja 文件。
subninja
相当于 #include
文件
default all
,指定默认的一个或多个target
ninja_required_version = 1.7.2
rule gn
command = ../../../../tools/gn --root=../../.. -q --dotfile=../../../build/lite/.gn --script-executable=python3 gen .
description = Regenerating ninja files
build build.ninja: gn
generator = 1
depfile = build.ninja.d
subninja toolchain.ninja
build ability: phony ./libability.so
build ability_notes: phony obj/foundation/aafwk/aafwk_lite/frameworks/ability_lite/ability_notes.stamp
#此处省略诸多 phony ..
build all: phony $
./libcameraApp.so $
obj/applications/sample/camera/cameraApp/cameraApp_hap.stamp $
./libgallery.so $
...
default all
toolchain.ninja
toolchain.ninja
定义了编译c,c++,汇编器,链接,静态/动态链接库,时间戳,拷贝等规则。
放置ninja
规则,将被subninja
进build.ninja
。
注意这些规则中的描述 description
字段,其后面的内容会打到控制台上,每一条输出都是一次build
,如图所示,通过这些描述就知道使用了什么规则去构建.
rule cxx
command = /root/llvm/bin/clang++ ${defines} ${include_dirs} ${cflags_cc} -c ${in} -o ${out}
description = clang++ ${out}
depfile = ${out}.d
deps = gcc
rule alink
command = /root/llvm/bin/llvm-ar -cr ${out} @"${out}.rsp"
description = AR ${out}
rspfile = ${out}.rsp
rspfile_content = ${in}
......
组件编译
每个组件都有自己的 .ninja
,描述组件的编译细节。整个 系统就是由众多的类似.ninja
构建编译完成的。
以编译 ability 组件为例说明 ninja 对组件的编译情况。
defines
,include_dirs
,cflags_cc
都是用户自定义变量,为了给rule cxx
准备参数- 对.cpp 的编译使用了规则
rule cxx
rule cxx
command = /root/llvm/bin/clang++ ${defines} ${include_dirs} ${cflags_cc} -c ${in} -o ${out}
description = clang++ ${out}
depfile = ${out}.d
deps = gcc
in
,out
是两个内置变量,无须定义,值由build提供,如此就编译成了一个个的.o
文件。- 在最后在当前目录下使用了
solink
规则,生成一个动态链接库libability.so
。
rule solink
command = /root/llvm/bin/clang -shared ${ldflags} ${in} ${libs} -o ${output_dir}/${target_output_name}${output_extension}
description = SOLINK ${output_dir}/${target_output_name}${output_extension}
rspfile = ${output_dir}/${target_output_name}${output_extension}.rsp
rspfile_content = ${in}