Android.mk

Android.mk | Android NDK | Android Developers (google.cn)

Android.mk入门教程 林栩link的博客

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定义为一个模块,然后再依赖相应的模块。

模块

一个模块。(具体的做一道菜)

  1. 准备食材,按照android编译系统的需求买菜。

包括LOCAL_SRC_FILESLOCAL_MODULELOCAL_STATIC_LIBRARIES等的收集。

  1. 烹饪。Google封装了不同菜的烹饪方式,提供了各种模板。

BUILD_STATIC_LIBRARYPREBUILT_SHARED_LIBRARYPREBUILT_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。

  1. 如果 D 是 //namespace:module 格式的完全限定名称,系统将仅在指定的命名空间中搜索指定的模块名称。

  2. 否则,Soong 将首先查找在命名空间 N 中声明的名为 D 的模块。

  3. 如果该模块不存在,Soong 会在命名空间 I1、I2、I3…中查找名为 D 的模块。

  4. 最后,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_typename="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构建系统 - 05 Android.mk .bp gn ninja全家桶-LMLPHP

  • 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:选择转换工具、选择解析框架、解析维护构建逻辑的“管家”就是。

Android构建系统 - 05 Android.mk .bp gn ninja全家桶-LMLPHP

  • 集成了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中变量和属性是强类型,变量根据第一项赋值动态变化,属性由模块类型静态设置,支持的类型为:

  • 布尔值 truefalse
  • 整数 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使用非常简单的动态类型语言。类型是:

  • 布尔(truefalse)。
  • 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_filewrite_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

行末的$是转义字符,并未真正换行;

  • output0output1是显示(explicit)输出,会出现在${out}列表中;
    |后的output2output3是隐式(implicit)输出,不会出现在${out}列表中;

  • rule_name是规则名称;

  • input0input1是显示依赖(输入),会出现在${in}列表中;
    |后的input2input3是隐式依赖,不会出现在${in}列表中;
    ||后的input4input5是隐式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规则,将被subninjabuild.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规则,将被subninjabuild.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 对组件的编译情况。

  • definesinclude_dirscflags_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
  • inout是两个内置变量,无须定义,值由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}
02-28 15:14