Android

[转载]android 8.1 安全机制 — SEAndroid & SELinux

0 条评论 Android 无标签 adtxl

版权声明:本文为CSDN博主「岁月斑驳7」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_19923217/article/details/81240027

1. SELinux背景知识

关于SELinux详细内容可参阅Google SELinux参考文档

1.1 DAC与MAC

在 SELinux 出现之前,Linux 上的安全模型叫 DAC,全称是 Discretionary Access Control,翻译为自主访问控制。

DAC 的核心思想很简单,就是:进程理论上所拥有的权限与执行它的用户的权限相同。比如,以 root 用户启动 Browser,那么 Browser 就有 root 用户的权限,在 Linux 系统上能干任何事情。

显然,DAD 管理太过宽松,只要想办法在 Android 系统上获取到 root 权限就可以了。那么 SELinux 是怎么解决这个问题呢?在 DAC 之外,它设计了一种新的安全模型,叫 MAC(Mandatory Access Control),翻译为强制访问控制。

MAC 的理论也很简单,任何进程想在 SELinux 系统上干任何事情,都必须在《安全策略文件》中赋予权限,凡是没有出现在安全策略文件中的权限,就不行。

关于 DAC 和 MAC,可以总结几个知识点:

  1. Linux 系统先做 DAC 检查。如果没有通过 DAC 权限检查,则操作直接失败。通过 DAC 检查之后,再做 MAC 权限检查
  2. SELinux 有自己的一套规则来编写安全策略文件,这套规则被称之为 SELinux Policy 语言。

1.2 SEPolicy语言

Linux中有两种东西,一种死的(Inactive),一种活的(Active)。死的东西就是文件(Linux哲学,万物皆文件。注意,万不可狭义解释为File),而活的东西就是进程。此处的 死 和 活 是一种比喻,映射到软件层面的意思是:进程能发起动作,例如它能打开文件并操作它。而文件只能被进程操作。

根据 SELinux 规范,完整的 Secure Context 字符串为:user:role:type[:range]

1.2.1 进程的Secure Context

在 SELinux 中,每种东西都会被赋予一个安全属性,官方说法叫做 Security Context,Security Context 是一个字符串,主要由三个部分组成,例如 SEAndroid 中,进程的 Security Context 可通过 ps -Z 命令查看:

rk3288:/ $ ps -AZ
u:r:hal_wifi_supplicant_default:s0 wifi      1816     1   11388   6972 0                   0 S wpa_supplicant
u:r:platform_app:s0:c512,c768  u0_a14        1388   228 1612844  57396 0                   0 S android.ext.services
u:r:system_app:s0              system        1531   228 1669680 119364 0                   0 S com.android.gallery3d
u:r:kernel:s0                  root           582     2       0      0 0                   0 S [kworker/1:2]
u:r:radio:s0                   radio          594   228 1634876  89296 0                   0 S com.android.phone
u:r:system_app:s0              system         672   228 1686204 141716 0                   0 S com.android.settings
u:r:platform_app:s0:c512,c768  u0_a18         522   223 1721656 152116 0                   0 S com.android.systemui

上面的最左边的一列就是进程的 Security Context,以第一个进程 wpa_supplicant 为例

u:r:hal_wifi_supplicant_default:s0

其中

  • u 为 user 的意思,SEAndroid 中定义了一个 SELinux 用户,值为 u
  • r 为 role 的意思,role 是角色之意,它是 SELinux 中一个比较高层次,更方便的权限管理思路。简单点说,一个 u 可以属于多个 role,不同的 role 具有不同的权限。
  • hal_wifi_supplicant_default 代表该进程所属的 Domain 为 hal_wifi_supplicant_default。MAC(Mandatory Access Control)强制访问控制 的基础管理思路其实是 Type Enforcement Access Control(简称TEAC,一般用TE表示),对进程来说,Type 就是 Domain,比如 hal_wifi_supplicant_default 需要什么权限,都需要通过 allow 语句在 te 文件中进行说明。
  • s0 是 SELinux 为了满足军用和教育行业而设计的 Multi-Level Security(MLS)机制有关。简单点说,MLS 将系统的进程和文件进行了分级,不同级别的资源需要对应级别的进程才能访问

1.2.2 文件的Secure Context

文件的 Secure Context 可以通过 ls -Z 来查看,如下

rk3288:/vendor/lib $ ls libOMX_Core.so -Z
u:object_r:vendor_file:s0 libOMX_Core.so
  • u:同样是 user 之意,它代表创建这个文件的 SELinux user
  • object_r:文件是死的东西,它没法扮演角色,所以在 SELinux 中,死的东西都用 object_r 来表示它的 role
  • vendor_file:type,和进程的 Domain 是一个意思,它表示 libOMX_Core.so 文件所属的 Type 是 vendor_file
  • s0:MLS 的等级

1.3 TE介绍

MAC 基本管理单位是 TEAC(Type Enforcement Accesc Control),然后是高一级别的RBAC(Role Based Accesc Control)。RBAC 是基于 TE 的,而 TE 也是 SELinux 中最主要的部分。上面说的 allow 语句就是 TE 的范畴。

根据 SELinux 规范,完整的 SELinux 策略规则语句格式为:

allow domains types:classes permissions;

- Domain - 一个进程或一组进程的标签。也称为域类型,因为它只是指进程的类型。
- Type - 一个对象(例如,文件、套接字)或一组对象的标签。
- Class - 要访问的对象(例如,文件、套接字)的类型。
- Permission - 要执行的操作(例如,读取、写入)。

= allow : 允许主体对客体进行操作
= neverallow :拒绝主体对客体进行操作
= dontaudit : 表示不记录某条违反规则的决策信息
= auditallow :记录某项决策信息,通常 SElinux 只记录失败的信息,应用这条规则后会记录成功的决策信息。

使用政策规则时将遵循的结构示例:

语句:
allow appdomain app_data_file:file rw_file_perms;

这表示所有应用域都可以读取和写入带有 app_data_file 标签的文件

相关实例

1. SEAndroid 中的安全策略文件 policy.conf
    # 允许 zygote 域中的进程向 init 域中的进程(Object Class 为 process)发送 sigchld 信号

    allow zygote init:process sigchld;

2. # 允许 zygote 域中的进程 search 或 getattr 类型为 appdomain 的目录。
   # 注意,多个 perm_set 可用 {} 括起来
    allow zygote appdomain:dir { getattr search };

3. # perm_set 语法比较奇特,前面有一个 ~ 号。
   # 它表示除了{entrypoint relabelto}之外,{chr_file #file}这两个object_class所拥有的其他操作 
    allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file }   \
    ~{entrypoint relabelto};

Object Class 类型

文件路径: system/sepolicy/private/security_classes

# file-related classes
class filesystem
class file  #代表普通文件
class dir   #代表目录
class fd    #代表文件描述符
class lnk_file  #代表链接文件
class chr_file  #代表字符设备文件

# network-related classes
class socket   #socket
class tcp_socket
class udp_socket

......
class binder   #Android 平台特有的 binder
class zygote   #Android 平台特有的 zygote

Perm Set类型
Perm Set 指得是某种 Object class 所拥有的权限。以 file 这种 Object class 而言,其拥有的 Perm Set 就包括 read、write、open、create、execute等。

文件路径: system/sepolicy/private/access_vectors

2. SELinux相关设置

2.1 强制执行等级

熟悉以下术语,了解如何按不同的强制执行级别实现 SELinux

  • 宽容模式(permissive) - 仅记录但不强制执行 SELinux 安全政策。
  • 强制模式(enforcing) - 强制执行并记录安全政策。如果失败,则显示为 EPERM 错误。

2.2 关闭SELinux

临时关闭
(1)setenforce

$ setenforce 0

setenforce 命令修改的是 /sys/fs/selinux/enforce 节点的值,是 kernel 意义上的修改 selinux 的策略。断电之后,节点值会复位

永久关闭
(2)kernel 关闭 selinux

SECURITY_SELINUX 设置为 false,重新编译 kernel

(3)设置 ro.boot.selinux=permissive 属性,并且修改在 system/core/init/Android.mk 中设置用于 user 版本下 selinux 模式为 permissive

ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
init_options += \
    -DALLOW_LOCAL_PROP_OVERRIDE=1 \
    -DALLOW_PERMISSIVE_SELINUX=1 \
    -DREBOOT_BOOTLOADER_ON_PANIC=1 \
    -DDUMP_ON_UMOUNT_FAILURE=1
else
init_options += \
    -DALLOW_LOCAL_PROP_OVERRIDE=0 \
    -DALLOW_PERMISSIVE_SELINUX=1 \ // 修改为1,表示允许 selinux 为 permissive
    -DREBOOT_BOOTLOADER_ON_PANIC=0 \
    -DDUMP_ON_UMOUNT_FAILURE=0
endif

enforcing - SELinux security policy is enforced.
permissive - SELinux prints warnings instead of enforcing.
disabled - No SELinux policy is loaded.

2.7 SELinux权限不足avc-denied问题解决

目前所有的 SELinux 权限检测失败,在 Kernel Log 或者 Android Log 中都有对应的 avc-denied Log 与之对应。反过来,有 avc-denied Log,并非就会直接失败,还需要确认当时 SELinux 的模式, 是 Enforcing 还是 Permissve。

如果是 Enforcing 模式,就要检测对应的进程访问权限资源是否正常?是否有必要添加? 如果有必要添加,可以按下面的方式生成需要的 sepolicy 规则并添加到对应 te 文件。

使用 audit2allow 简化方法

  1. 从 logcat 或串口中提取相应的 avc-denied log,下面的语句为提取所有的 avc- denied log

    $ adb shell "cat /proc/kmsg | grep avc" > avc_log.txt 
  2. 使用 audit2allow 工具生成对应的 policy 规则

    // audio2allow 使用必须先 source build/envsetup.sh,导入环境变量
    $ audit2allow -i avc_log.txt -p $OUT/vendor/etc/selinux/precompiled_sepolicy
  3. 将对应的policy 添加到 te 文件中

    一般添加在 /device/<company>/common/sepolicy 或者 /device/<company>/$DEVICE/sepolicy 目录下

    BOARD_SEPOLICY_DIRS += device/$SoC/common/sepolicy 通过这个命令添加厂家自定义的 sepolicy 规则

3. SEAndroid安全机制框架

SELinux 系统比起通常的 Linux 系统来,安全性能要高的多,它通过对于用户,进程权限的最小化,即使受到攻击,进程或者用户权限被夺去,也不会对整个系统造成重大影响。

我们知道,Android 系统是基于 Linux 内核实现,针对 Linux 系统,NSA 开发了一套安全机制 SELinux,用来加强安全性。然而,由于 Android 系统有着独特的用户空间运行时,因此 SELinux 不能完全适用于 Android 系统。为此,NSA 针对 Android 系统,在 SELinux 基础上开发了 SEAndroid。

SEAndroid 安全机制所要保护的对象是系统中的资源,这些资源分布在各个子系统中,例如我们经常接触的文件就是分布文件子系统中的。实际上,系统中需要保护的资源非常多,除了前面说的文件之外,还有进程、socket 和 IPC 等等。对于 Android 系统来说,由于使用了与传统 Linux 系统不一样的用户空间运行时,即应用程序运行时框架,因此它在用户空间有一些特有的资源是需要特别保护的,例如系统属性的设置等。

3.1 SEAndroid框架流程

SEAndroid 安全机制的整体框架,可以使用下图来概括:
image.png

以 SELinux 文件系统接口为边界,SEAndroid 安全机制包含内核空间和用户空间两部分支持。

1. 内核空间的 SELinux LSM 模块负责内核资源的安全访问控制
2. 用户空间的 SEAndroid Policy 描述的是资源安全访问策略。
   系统在启动的时候,用户空间的 Security Server 需要将这些安全访问策略加载内核空间的 SELinux LSM 模块中去。
   这是通过SELinux文件系统接口实现的
3. 用户空间的 Security Context 描述的是资源安全上下文。
   SEAndroid 的安全访问策略就是在资源的安全上下文基础上实现的
4. 用户空间的 Security Server 一方面需要到用户空间的 Security Context 去检索对象的安全上下文,
   另一方面也需要到内核空间去操作对象的安全上下文
5. 用户空间的 libselinux 库封装了对 SELinux 文件系统接口的读写操作。
   用户空间的 Security Server 访问内核空间的 SELinux LSM 模块时,都是间接地通过 libselinux进行的。
   这样可以将对 SELinux 文件系统接口的读写操作封装成更有意义的函数调用。
6. 用户空间的 Security Server 到用户空间的 Security Context 去检索对象的安全上下文时,同样也是通过 selinux 库来进行的

3.1.1 内核空间

在内核空间,存在一个 SELinux LSM(Linux Secrity Moudle)模块,(用 MAC 强制访问控制)负责资源的安全访问控制。

LSM 模块中包含一个访问向量缓冲(Access Vector Cache)和一个安全服务(Security Server)。Security Server 负责安全访问控制逻辑,即由它来决定一个主体访问一个客体是否是合法的,这个主体一般是指进程,而客体主要指资源,例如文件。

SELinux、LSM 和内核中的子系统是如何交互的呢?首先,SELinux 会在 LSM 中注册相应的回调函数。其次,LSM 会在相应的内核对象子系统中会加入一些 Hook 代码。例如,我们调用系统接口 read 函数来读取一个文件的时候,就会进入到内核的文件子系统中。在文件子系统中负责读取文件函数 vfs_read 就会调用 LSM 加入的 Hook 代码。这些 Hook 代码就会调用之前 SELinux 注册进来的回调函数,以便后者可以进行安全检查。

SELinux 在进行安全检查的时候,首先是看一下自己的 Access Vector Cache 是否已经有结果。如果有的话,就直接将结果返回给相应的内核子系统就可以了。如果没有的话,就需要到 Security Server 中去进行检查。检查出来的结果在返回给相应的内核子系统的同时,也会保存在自己的 Access Vector Cache 中,以便下次可以快速地得到检查结果

上面概述的安全访问控制流程,可以使用下图来总结:
image76d34b3ae9f72d50.png

1. 一般性错误检查,例如访问的对象是否存在、访问参数是否正确等
2. DAC 检查,即基于 Linux UID/GID 的安全检查
3. SELinux 检查,即基于安全上下文和安全策略的安全检查

3.1.2 用户空间

在用户空间,SEAndorid 主要包含三个模块,分别是安全上下文(Security Context)、安全策略(SEAndroid Policy)和安全服务(Security Server)。

(1)安全上下文
前面已经描述过了,SEAndroid 是一种基于安全策略的 MAC 安全机制。这种安全策略又是建立在对象的安全上下文的基础上的,SEAndroid 中的对象分为主体(Subject)和客体(Object),主体通常是指进程,而客体是指进程所要访问的资源,如文件、系统属性等

安全上下文实际上就是一个附加在对象上的标签(Tag)。这个标签实际上就是一个字符串,它由四部分内容组成,分别是 SELinux 用户、SELinux 角色、类型、安全级别,每一个部分都通过一个冒号来分隔,格式为

"user:role:type:sensitivity"

用 ps -Z 来查看主体的安全上下文,而用 ls -Z 来查看客体的安全上下文。

安全上下文<类型>说明
通常将用来标注文件的安全上下文中的类型称为 file_type,用来标注进程的安全上下文的类型称为 domain。

将两个类型相关联可以通过 type 语句实现,例如用来描述 init 进程安全策略文件 /system/sepolicy/public/init.te 文件中,使用 type 语句将 init 与 domain 相关联(等价)

type init domain;
// 这样就可以表明 init 描述的类型是用来描述进程的安全上下文的

Android 系统中对象的安全上下文定义
系统中各种类型对象(包含主体和客体)的安全上下文是在 system/sepolicy 工程中定义的,我们讨论四种类型对象的安全上下文,分别是 app 进程、app 数据文件、系统文件和系统属性。这四种类型对象的安全上下文通过四个文件来描述:mac_permissions.xml、seapp_contexts、file_contexts 和 property_contexts。

mac_permissions 文件给不同签名的 app 分配不同的 seinfo 字符串,例如,在 AOSP 源码环境下编译并且使用平台签名的 App 获得的 seinfo 为 “platform”,使用第三方签名安装的 App 获得的 seinfo 签名为”default”。

<!-- Platform dev key in AOSP -->
<signer signature="@PLATFORM" >
  <seinfo value="platform" />
</signer>

<!-- Media key in AOSP -->
<signer signature="@MEDIA" >
  <seinfo value="media" />
</signer>

这里的 seinfo 描述的并不是安全上下文的对象类型,它用来在 seapp_contexts 中查找对应的对象类型。在 seapp_contexts 对 seinfo 也就是平台签名为”platform”的 app 如下定义:

user=_app seinfo=platform domain=platform_app type=app_data_file levelFrom=user

也就是说明使用"platform"平台签名的 app 所运行的进程 domain 为"platform_app",
并且他的数据文件类型为"app_data_file"。****

接下来看一下系统文件的安全上下文是如何定义的:

###########################################
# Root
/                   u:object_r:rootfs:s0

# Data files
/adb_keys           u:object_r:adb_keys_file:s0
/build\.prop        u:object_r:rootfs:s0
/default\.prop      u:object_r:rootfs:s0
/fstab\..*          u:object_r:rootfs:s0
/init\..*           u:object_r:rootfs:s0
/res(/.*)?          u:object_r:rootfs:s0
/selinux_version    u:object_r:rootfs:s0
/ueventd\..*        u:object_r:rootfs:s0
/verity_key         u:object_r:rootfs:s0

可以看到使用正则表达式描述了系统文件的安全上下文。

在 Android 系统中有一种比较特殊的权限——属性,app 能够读写他们获取相应的系统信息以及控制系统的行为。因此不同与 SELinux,SEAndroid 中对系统属性也进行了保护,这意味着 Android 系统的属性也需要关联安全上下文。这是在 property_contexts 文件中描述的

##########################
# property service keys
#
#
net.rmnet               u:object_r:net_radio_prop:s0
net.gprs                u:object_r:net_radio_prop:s0
net.ppp                 u:object_r:net_radio_prop:s0
net.qmi                 u:object_r:net_radio_prop:s0
net.lte                 u:object_r:net_radio_prop:s0
net.cdma                u:object_r:net_radio_prop:s0
net.dns                 u:object_r:net_dns_prop:s0
sys.usb.config          u:object_r:system_radio_prop:s0
ril.                    u:object_r:radio_prop:s0
ro.ril.                 u:object_r:radio_prop:s0
gsm.                    u:object_r:radio_prop:s0
persist.radio           u:object_r:radio_prop:s0

这边的 net.rmnet 行类型为 net_radio_prop,意味着只有有权限访问 net_radio_prop 的进程才可以访问这个属性。

(2) 安全策略
SEAndroid 安全机制中的安全策略是在安全上下文的基础上进行描述的,也就是说,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。

Type Enforcement
SEAndroid 安全机制主要是使用对象安全上下文中的类型来定义安全策略,这种安全策略就称 Type Enforcement,简称TE。在 system/sepolicy 目录和其他所有客制化 te 目录(通常在 device//common,用 BOARD_SEPOLICY_DIRS 添加),所有以 .te 为后缀的文件经过编译之后,就会生成一个 sepolicy 文件。这个 sepolicy 文件会打包在ROM中,并且保存在设备上的根目录下。
一个 Type 所具有的权限是通过allow语句来描述的

allow unconfineddomain domain:binder { call transfer set_context_mgr };

表明 domain 为 unconfineddomain 的进程可以与其它进程进行 binder ipc 通信(call),并且能够向这些进程传递 Binder 对象(transfer),以及将自己设置为 Binder 上下文管理器(set_context_mgr)。

注意,SEAndroid 使用的是最小权限原则,也就是说,只有通过 allow 语句声明的权限才是允许的,而其它没有通过 allow 语句声明的权限都是禁止,这样就可以最大限度地保护系统中的资源

前面我们提到,SEAndroid 安全机制的安全策略经过编译之后会得到一个 sepolicy 文件,并且最终保存在设备上的根目录上。这个 sepolicy 文件中的安全策略是不会自动加载的到内核空间的 SELinux LSM 模块中去的,它需要我们在系统启动的时候进行加载。

(3)Security Server
Security Server 是一个比较笼统的概念,主要是用来保护用户空间资源的,以及用来操作内核空间对象的安全上下文的,它由应用程序安装服务 PackageManagerService、应用程序安装守护进程 installd、应用程序进程孵化器 Zygote 进程以及 init 进程组成。其中,PackageManagerService 和 installd 负责创建 App 数据目录的安全上下文,Zygote 进程负责创建 App 进程的安全上下文,而 init 进程负责控制系统属性的安全访问。

PackageManagerService & installed —— app 数据目录的安全上下文
PackageManagerService 在启动的时候,会找到我们前面分析的 mac_permissions.xml 文件,然后对它进行解析,得到 App 签名或者包名与 seinfo 的对应关系。当 PackageManagerService 安装 App 的时候,它就会根据其签名或者包名查找到对应的 seinfo,并且将这个 seinfo 传递给另外一个守护进程 installed。

守护进程 installd 负责创建 App 数据目录。在创建 App 数据目录的时候,需要给它设置安全上下文,使得 SEAndroid 安全机制可以对它进行安全访问控制。Installd 根据 PackageManagerService 传递过来的 seinfo,并且调用 libselinux 库提供的 selabel_lookup 函数到前面我们分析的 seapp_contexts 文件中查找到对应的 Type。有了这个 Type 之后,installd 就可以给正在安装的 App 的数据目录设置安全上下文了,这是通过调用 libselinux 库提供的 lsetfilecon 函数来实现的。

Zygote —— 设置进程的安全上下文
在 Android 系统中,Zygote 进程负责创建应用程序进程。应用程序进程是 SEAndroid 安全机制中的主体,因此它们也需要设置安全上下文,这是由 Zygote 进程来设置的。

ActivityManagerService 在请求 Zygote 进程创建应用程序进程之前,会到 PackageManagerService 中去查询对应的 seinfo,并且将这个 seinfo 传递到 Zygote 进程。于是,Zygote 进程在 fork 一个应用程序进程之后,就会使用 ActivityManagerService 传递过来的 seinfo,并且调用 libselinux 库提供的 selabel_lookup 函数到前面我们分析的 seapp_contexts 文件中查找到对应的 Domain。有了这个 Domain 之后,Zygote 进程就可以给刚才创建的应用程序进程设置安全上下文了,这是通过调用 libselinux 库提供的 lsetcon 函数来实现的。

init —— 系统属性的安全上下文
init 进程在启动的时候会创建一块内存区域来维护系统中的属性,接着还会创建一个 Property 服务系统,这个服务系统通过 socket 提供接口给其他进程访问 android 系统中的属性。

其他进程通过 socket 和属性系统通信请求访问某项系统属性的值,属性服务系统可以通过 libselinux 库提供的 selabel_lookup 函数到前面我们分析的 property_contexts 中查找要访问的属性的安全上下文了。有了该进程的安全上下文和要访问属性的安全上下文之后,属性系统就能决定是否允许一个进程访问它所指定的服务了。

4. SEAndroid源码分析

4.1 SEAndroid源码架构

- externel/selinux:包含编译 sepolicy 策略文件的一些实用构建工具
    - external/selinux/libselinux:提供了帮助用户进程使用 SELinux 的一些函数
    - external/selinux/libsepol:提供了供安全策略文件编译时使用的一个工具 checkcon
- system/sepolicy:包含 Android SELinux 核心安全策略(te 文件),编译生成 sepolicy 文件
    - file_contexts: 系统中所有文件的安全上下文
    - property_contexts: 系统中所有属性的安全上下文
    - seapp_contexts:定义用户、seinfo和域之间的关系,用于确定用户进程的安全上下文
    - sepolicy:二进制文件,保存系统安全策略,系统初始化时会把它设置到内核中
SELinux 虚拟文件系统在 sys/fs/selinux 下,该目录下的文件是 SELinux 内核和用户进程进行通信的接口,libselinux 就是利用这边的接口进行操作

4.2 init进程SEAndroid启动源码分析

文件路径:/system/core/init/init.cpp

int main(int argc, char** argv) {
    ...
    if (is_first_stage) {
        ... 

        // Set up SELinux, loading the SELinux policy.
        selinux_initialize(true); 

        // We're in the kernel domain, so re-exec init to transition to the init domain now
        // that the SELinux policy has been loaded.
        if (selinux_android_restorecon("/init", 0) == -1) {
            PLOG(ERROR) << "restorecon failed";
            security_failure();
        }
        ...
    }
}

可以看到 SEAndroid 的启动设置在 init 进程内核态执行(first_stage)过程中,selinux_initialize 函数就是主要的初始化过程,包含加载 sepolicy 策略文件到内核的 LSM 模块中。

文件路径:/system/core/init/init.cpp

static void selinux_initialize(bool in_kernel_domain) {
    Timer t;

    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    // 标识 init 进程的内核态执行和用户态执行
    if (in_kernel_domain) {
        LOG(INFO) << "Loading SELinux policy";
        if (!selinux_load_policy()) {
            panic();
        }

        bool kernel_enforcing = (security_getenforce() == 1);
        bool is_enforcing = selinux_is_enforcing();
        if (kernel_enforcing != is_enforcing) {
            if (security_setenforce(is_enforcing)) {
                PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
                security_failure();
            }
        }

        std::string err;
        if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) {
            LOG(ERROR) << err;
            security_failure();
        }

        // init's first stage can't set properties, so pass the time to the second stage.
        setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
    } else {
        selinux_init_all_handles();
    }
}

selinux_set_callback
selinux_set_callback 用来向 libselinux 设置 SEAndroid 日志和审计回调函数

selinux_load_policy
这个函数用来加载 sepolicy 策略文件,并通过 mmap 映射的方式将 sepolicy 的安全策略加载到 SELinux LSM 模块中去。
下面具体分析一下流程:

文件路径:system/core/init/init.cpp

static bool selinux_load_policy() {
    return selinux_is_split_policy_device() ? selinux_load_split_policy()
                                            : selinux_load_monolithic_policy();
}

这里区分了从哪里加载安全策略文件,第一个是从 /vendor/etc/selinux/precompiled_sepolicy 读取,第二个是直接从根目录 /sepolicy
中读取,最终调用的方法都是 selinux_android_load_policy_from_fd

int selinux_android_load_policy_from_fd(int fd, const char *description)
{
    int rc;
    struct stat sb;
    void *map = NULL;
    static int load_successful = 0;

    /*
     * Since updating policy at runtime has been abolished
     * we just check whether a policy has been loaded before
     * and return if this is the case.
     * There is no point in reloading policy.
     */
    if (load_successful){
      selinux_log(SELINUX_WARNING, "SELinux: Attempted reload of SELinux policy!/n");
      return 0;
    }

    set_selinuxmnt(SELINUXMNT);
    if (fstat(fd, &sb) < 0) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not stat %s:  %s\n",
                description, strerror(errno));
        return -1;
    }
    map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not map %s:  %s\n",
                description, strerror(errno));
        return -1;
    }

    rc = security_load_policy(map, sb.st_size);
    if (rc < 0) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not load policy:  %s\n",
                strerror(errno));
        munmap(map, sb.st_size);
        return -1;
    }

    munmap(map, sb.st_size);
    selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", description);
    load_successful = 1;
    return 0;
}

(1)set_selinuxmnt 函数设置内核 SELinux 文件系统路径,这里的值为 /sys/fs/selinux,SELinux 文件系统用来与内核空间 SELinux LSM 模块空间。

(2)通过 fstat 获取 sepolicy 文件(fd 值)的状态信息,通过 mmap 函数将文件内容映射到内存中,起始地址为 map

(3)security_load_policy 调用另一个 security_load_policy 函数将已经映射到内存中的 SEAndroid 的安全策略加载到内核空间的 SELinux LSM 模块中去。

int security_load_policy(void *data, size_t len)
{
    char path[PATH_MAX];
    int fd, ret;

    if (!selinux_mnt) {
        errno = ENOENT;
        return -1;
    }

    snprintf(path, sizeof path, "%s/load", selinux_mnt);
    fd = open(path, O_RDWR | O_CLOEXEC);
    if (fd < 0)
        return -1;

    ret = write(fd, data, len);
    close(fd);
    if (ret < 0)
        return -1;
    return 0;
}

函数 security_load_policy 的实现很简单,它首先打开 /sys/fs/selinux/load 文件,然后将参数 data 所描述的安全策略写入到这个文件中去。由于 /sys/fs/selinux 是由内核空间的 SELinux LSM 模块导出来的文件系统接口,因此当我们将安全策略写入到位于该文件系统中的 load 文件时,就相当于是将安全策略从用户空间加载到 SELinux LSM 模块中去了。

以后 SELinux LSM 模块中的 Security Server 就可以通过它来进行安全检查

(4)加载完成,释放 sepolicy 文件占用的内存,并且关闭 sepolicy 文件
security_setenforce
前面已经提过,selinux 有两种工作模式:

  • 宽容模式(permissive) - 仅记录但不强制执行 SELinux 安全政策。
  • 强制模式(enforcing) - 强制执行并记录安全政策。如果失败,则显示为 EPERM 错误。

这个函数用来设置 kernel SELinux 的模式,实际上都是去操作 /sys/fs/selinux/enforce 文件, 0 表示permissive,1 表示 enforcing

selinux_init_all_handles
在 init 进程的用户态启动过程中会调用这个函数初始化 file_context、 property_context 相关内容 handler,根据前面的描述,init 进程给一些文件或者系统属性进行安全上下文检查时会使用 libselinux 的 API,查询文件根目录下 file_context、file_context 的安全上下文内容。所以需要先得到这些文件的 handler,以便可以用来查询系统文件和系统属性的安全上下文。

static void selinux_init_all_handles(void)
{
    sehandle = selinux_android_file_context_handle();
    selinux_android_set_sehandle(sehandle);
    sehandle_prop = selinux_android_prop_context_handle();
}

待看:
https://www.jianshu.com/p/4780e22b5446?from=timeline&isappinstalled=0


Android系统之VINTF(1)简介

0 条评论 Android 无标签 adtxl

1. 概述

VINTF对象(vendor interface object),即供应商接口对象,该对象用于汇总设备的相关信息,并通过可查询的 API 提供相关信息。
Framework(system)和Device(vendor)匹配的框架如下:
image.png

VINTF 对象设计为设备和框架组件提供以下内容:

For the Decice For the Framework
- Defines a schema for the static component (the device manifest file).
- Adds build time support for defining the device manifest file for a given device.
- Defines the queryable API at runtime that retrieves the device manifest file (along with the other runtime-collectible information) and packages them into the query result.
- Defines a schema for the static component (the framework manifest file).
- Defines the queryable API at runtime that retrieves the framework manifest file and packages it into the query result.

无论何时请求,VINTF 对象都必须稳定可靠且能提供相同的完整信息请见注意事项

2.Manifests&matrixes

从 Android 8.0 开始,运行时 API 会查询设备上的内容并将该信息发送到无线下载 (OTA) 更新服务器以及其他相关方(例如 CTS DeviceInfo)。有些信息会在运行时进行检索,而有些信息则是静态定义的信息。

  • The device manifest describes the static component of what the device can provide to the framework.
  • The framework compatibility matrix describes what the Android framework expects from a given device. The matrix is a static entity whose composition is determined manually during development of the next release of the Android framework.
  • The framework manifest describes high-level services the framework can provide to the device.
  • The device compatibility matrix describes the services the vendor image requires of the framework. Its composition is determined manually during the development of the device.

Manifest 描述了提供给对方的feature, Matrix 描述了需要对方提供的feature。Manifest 和 Matrix 在OTA升级前会进行匹配检查,以确保framework和device是兼容的。总的来说,manifest是提供端,matrix是需求端。


添加新设备类型-Android编译系统(4)

0 条评论 Android 无标签 adtxl

1.理解构建层

构建层次结构包括与设备的物理结构对应的抽象层。下表中介绍了这些层。每一层都与上一层存在一对多的关系。例如,一个架构(ARCH)可以有多个主板(Board/device),一个主板可以有多个产品(Product)。您可以将指定层中的某个元素定义为同一层中某个元素的特化元素,这样可以免去复制操作并简化维护工作。

Layer Example Description
Product myProduct, myProduct_eu, myProduct_eu_fr, j2, sdk The product layer defines the feature specification of a shipping product such as the modules to build, locales supported, and configuration for various locales. In other words, this is the name of the overall product. Product-specific variables are defined in product definition makefiles. A product can inherit from other product definitions, which simplifies maintenance. A common method is to create a base product that contains features that apply to all products, then create product variants based on that base product. For example, two products that differ only by their radios (CDMA versus GSM) can inherit from the same base product that doesn't define a radio.
Board/device marlin, blueline, coral The board/device layer represents the physical layer of plastic on the device (that is, the industrial design of the device). This layer also represents the bare schematics of a product. These include the peripherals on the board and their configuration. The names used are merely codes for different board/device configurations.
Arch arm, x86, arm64, x86_64 The architecture layer describes the processor configuration and application binary interface (ABI) running on the board.

2.使用build variants

在针对特定产品进行构建时,如果能在最终发布 build 的基础上有细微的变化,会非常有用。在模块定义中,模块可以通过 LOCAL_MODULE_TAGS 指定标记,这些标记可以是以下一个或多个值:optional(默认值)、debug 和 eng。

如果某个模块没有通过 LOCAL_MODULE_TAGS 指定标记,则其标记默认设置为 optional。仅当 PRODUCT_PACKAGES 的产品配置需要可选模块时,系统才会安装可选模块。
下面是当前定义的构建变体(build variants)。

Variant Description
eng This is the default flavor.
- Installs modules tagged with eng or debug.
- Installs modules according to the product definition files, in addition to tagged modules.
- ro.secure=0
- ro.debuggable=1
- ro.kernel.android.checkjni=1
- adb is enabled by default.
user The variant intended to be the final release bits.
- Installs modules tagged with user.
- Installs modules according to the product definition files, in addition to tagged modules.
- ro.secure=1
- ro.debuggable=0
- adb is disabled by default.
userdebug The same as user, with these exceptions:
- Also installs modules tagged with debug.
- ro.debuggable=1
- adb is enabled by default.

aosp为build系统提供三种Product配置,文档里叫做build variants,分别是:

  • eng : 对应到工程版。编译打包所有模块。表示adbd处于ROOT状态,所有调试开关打开
  • userdebug : 对应到用户调试版。打开调试开关,但并没有放开ROOT权限
  • user : 对应到用户版。关闭调试开关,关闭ROOT权限。最终发布到用户手上的版本,通常都是user版。

2.1 userdebug的准则

在测试中运行 userdebug build 有助于设备开发者了解开发中版本的性能和功耗。为了让 user build 和 userdebug build 保持一致,并在用于调试的 build 中获得可靠的指标,设备开发者应遵循以下准则:

  • userdebug 定义为已启用 root 权限的 user build,但以下情况除外:
    • 仅由用户视需要运行且仅用于 userdebug build 的应用
    • 仅在空闲维护(连接充电器/充满电)期间执行的操作,例如,使用 dex2oatd 而不是 dex2oat 来进行后台编译
  • 不要添加根据构建类型默认启用/停用的功能。建议开发者不要使用任何影响电池续航时间的日志记录形式(例如调试日志记录或堆转储)。
  • 在 userdebug build 中默认启用的任何调试功能都应明确定义,并告知处理相关项目的所有开发者。您应该只在限定的时间内启用调试功能,直到您尝试调试的问题得到解决。

3. Customizing the build with resource overlays(利用资源叠加层自定义build)

Android 构建系统会在构建时使用资源叠加层来自定义产品。资源叠加层用于指定在默认文件之上应用的资源文件。如需使用资源叠加层,请修改项目构建文件,将 PRODUCT_PACKAGE_OVERLAYS 设为相对于顶级目录的路径。当构建系统搜索资源时,该路径会变为影子根目录,系统除了在当前根目录中进行搜索外,还会一并在该路径中进行搜索。
最常自定义的设置包含在 frameworks/base/core/res/res/values/config.xml 文件中。
如需在此文件上设置资源叠加层,请使用以下某个命令将叠加层目录添加到项目构建文件:

PRODUCT_PACKAGE_OVERLAYS := device/device-implementer/device-name/overlay

例如:
image1d68019ddc9ecdf6.png

PRODUCT_PACKAGE_OVERLAYS := vendor/vendor-name/overlay

然后,将一个叠加层文件添加到该目录下,例如:

vendor/foobar/overlay/frameworks/base/core/res/res/config.xml

在叠加层 config.xml 文件中找到的所有字符串或字符串数组都会替换在原始文件中找到的对应字符串或字符串数组。

4. 构建产品

您可以通过多种不同的方式来组织设备的源文件。下面简要说明了 Pixel 实现的一种组织方式。

为 Pixel 实现名为 marlin 的主设备配置。根据此设备配置,为产品创建产品定义 Makefile,用于声明关于设备的产品特定信息,例如名称和型号。您可以查看 device/google/marlin 目录,了解所有相关配置的具体设置方式。

4.1 编写产品Makefile

以下步骤介绍了如何采用与设置 Pixel 产品线类似的方式设置产品 Makefile:

  1. 为您的产品创建一个device/<company-name>/<device-name> 目录。例如,device/google/marlin。此目录将包含您设备的源代码以及构建这些代码所需的 Makefile。
  2. 创建一个 device.mk Makefile,用来声明设备所需的文件和模块。有关示例,请查看 device/google/marlin/device-marlin.mk
  3. 创建一个产品定义 Makefile,以便基于设备创建具体产品。以下示例 Makefile 来自于 device/google/marlin/aosp_marlin.mk。请注意,该产品会通过 Makefile 沿用 device/google/marlin/device-marlin.mk 和 vendor/google/marlin/device-vendor-marlin.mk 文件中的设置,同时还会声明产品特定信息,例如名称、品牌和型号。
# Inherit from the common Open Source product configuration
$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_base_telephony.mk)

PRODUCT_NAME := aosp_marlin
PRODUCT_DEVICE := marlin
PRODUCT_BRAND := Android
PRODUCT_MODEL := AOSP on msm8996
PRODUCT_MANUFACTURER := Google
PRODUCT_RESTRICT_VENDOR_FILES := true

PRODUCT_COPY_FILES += device/google/marlin/fstab.common:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.marlin

$(call inherit-product, device/google/marlin/device-marlin.mk)
$(call inherit-product-if-exists, vendor/google_devices/marlin/device-vendor-marlin.mk)

PRODUCT_PACKAGES += \
    Launcher3QuickStep \
    WallpaperPicker

如需了解可添加到 Makefile 的其他产品特定变量,请参阅设置产品定义变量(4.2小节)。

  1. 创建一个指向产品的 Makefile文件AndroidProducts.mk。在此示例中,仅需要产品定义 Makefile。以下示例来自于 device/google/marlin/AndroidProducts.mk(该文件同时包含 marlin (Pixel) 和 sailfish (Pixel XL),它们共享大部分配置):
PRODUCT_MAKEFILES := \
    $(LOCAL_DIR)/aosp_marlin.mk \
    $(LOCAL_DIR)/aosp_sailfish.mk

COMMON_LUNCH_CHOICES := \
    aosp_marlin-userdebug \
    aosp_sailfish-userdebug
  1. 创建一个包含主板特定配置的Makefile文件BoardConfig.mk 。如需查看示例,请查看 device/google/marlin/BoardConfig.mk。

  2. 创建一个 vendorsetup.sh 文件,以便将您的产品(“午餐套餐”)与构建变体(使用短划线将两者分隔开)一起添加到 build 中。例如:

    add_lunch_combo <product-name>-userdebug

    这一步显得多余了,谷歌已经不建议这样了

  3. 这时,您就可以基于同一设备创建更多产品变体了。

4.2 设置产品定义变量

产品特定变量在产品的 Makefile 中进行定义。下表显示了在产品定义文件中维护的部分变量。

变量 说明 示例
PRODUCT_AAPT_CONFIG aapt configurations to use when creating packages.
PRODUCT_BRAND The brand (for example, carrier) the software is customized for, if any.
PRODUCT_CHARACTERISTICS aapt characteristics to allow adding variant-specific resources to a package. tablet, nosdcard
PRODUCT_COPY_FILES List of words like source_path:destination_path. The file at the source path should be copied to the destination path when building this product. The rules for the copy steps are defined in config/makefile.
PRODUCT_DEVICE Name of the industrial design. This is also the board name, and the build system uses it to locate BoardConfig.mk. tuna
PRODUCT_LOCALES A space-separated list of two-letter language code, two-letter country code pairs that describe several settings for the user, such as the UI language and time, date, and currency formatting. The first locale listed in PRODUCT_LOCALES is used as the product's default locale. en_GB, de_DE, es_ES, fr_CA
PRODUCT_MANUFACTURER Name of the manufacturer. acme
PRODUCT_MODEL End-user-visible name for the end product.
PRODUCT_NAME End-user-visible name for the overall product. Appears in the Settings > About screen.
PRODUCT_OTA_PUBLIC_KEYS List of over-the-air (OTA) public keys for the product.
PRODUCT_PACKAGES List of the APKs and modules to install. Calendar contacts
PRODUCT_PACKAGE_OVERLAYS Indicates whether to use default resources or add any product specific overlays. vendor/acme/overlay
PRODUCT_SYSTEM_PROPERTIES List of the system property assignments in the format "key=value" for the system partition. System properties for other partitions can be set via PRODUCT_<PARTITION>_PROPERTIES as in PRODUCT_VENDOR_PROPERTIES for the vendor partition. Supported partition names: SYSTEM, VENDOR, ODM, SYSTEM_EXT, and PRODUCT.

4.3 设置ADB_VENDOR_KEYS以通过USB进行连接

借助 ADB_VENDOR_KEYS 环境变量,设备制造商无需手动授权,即可通过 adb 访问可调试的 build(userdebug build 和 eng build,但不能访问 user build)。通常,adb 会为每台客户端计算机生成一个唯一的 RSA 身份验证密钥,并将其发送到所有已连接的设备。这就是 adb 授权对话框中显示的 RSA 密钥。或者,您也可以将已知密钥构建到系统映像中,并将其分享给相应的 adb 客户端。这对于操作系统开发来说很有用,对测试来说尤其有用,因为此做法无需与 adb 授权对话框手动进行交互。

如需创建供应商密钥,应安排一个人(通常为发布管理员)执行以下操作:

  1. 使用 adb keygen 生成密钥对。对于 Google 设备,Google 会为每个新操作系统版本生成一个新密钥对。
  2. 将密钥对签入源代码树中的某个位置。例如,Google 会将其存储在 vendor/google/security/adb/ 中。
  3. 设置构建变体 PRODUCT_ADB_KEYS,使其指向您的密钥目录。为此,Google 会在密钥目录中添加一个 Android.mk 文件,其内容为 PRODUCT_ADB_KEYS := $(LOCAL_PATH)/$(PLATFORM_VERSION).adb_key.pub,以帮助确保我们记得为每个操作系统版本生成新的密钥对。

以下是 Google 在该目录(我们将各版本的已签入密钥对存储在此处)中使用的 Makefile:

PRODUCT_ADB_KEYS := $(LOCAL_PATH)/$(PLATFORM_VERSION).adb_key.pub

ifeq ($(wildcard $(PRODUCT_ADB_KEYS)),)
  $(warning ========================)
  $(warning The adb key for this release)
  $(warning )
  $(warning   $(PRODUCT_ADB_KEYS))
  $(warning )
  $(warning does not exist. Most likely PLATFORM_VERSION in build/core/version_defaults.mk)
  $(warning has changed and a new adb key needs to be generated.)
  $(warning )
  $(warning Please run the following commands to create a new key:)
  $(warning )
  $(warning   make -j8 adb)
  $(warning   LOGNAME=android-eng HOSTNAME=google.com adb keygen $(patsubst %.pub,%,$(PRODUCT_ADB_KEYS)))
  $(warning )
  $(warning and upload/review/submit the changes)
  $(warning ========================)
  $(error done)
endif

如需使用这些供应商密钥,工程师只需将 ADB_VENDOR_KEYS 环境变量设为指向存储相应密钥对的目录。这会告知 adb 先尝试这些规范密钥,然后回退到需要手动授权的已生成主机密钥。当 adb 无法连接到未获授权的设备时,系统会显示错误消息,提示您设置 ADB_VENDOR_KEYS(如果尚未设置)。

参考:

Android系统之添加Product
Adding a New Device


[转载]编译环境初始化-Android10.0编译系统(二)

0 条评论 Android 无标签 adtxl

版权声明:本文为CSDN博主「IngresGe」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yiranfeng/article/details/109083754

1. 概述

上一节针对Android编译系统做了一个笼统的说明,这一节针对编译环境初始化做一下详细的展示。

2. 编译环境初始化

初始化命令:

source build/envsetup.sh

envsetup.sh 主要做了下面几个事情:
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-3.jpg

envsetup.sh 构建代码:

   ...

  validate_current_shell

  source_vendorsetup

  addcompletions

2.1 hmm查看支持接口

输入hmm可以看到envsetup支持的一些接口

命令 说明
lunch lunch <product_name>-<build_variant>选择<product_name>作为要构建的产品,<build_variant>作为要构建的变体,并将这些选择存储在环境中,以便后续调用“m”等读取。
tapas 交互方式:tapas [<App1> <App2> ...] [arm\x86\mips\arm64\x86_64\mips64] [eng\userdebug\user]
croot 将目录更改到树的顶部或其子目录。
m 编译整个源码,可以不用切换到根目录
mm 编译当前目录下的源码,包含他们的依赖模块(11包含)
mmm 编译指定目录下的所有模块,包含他们的依赖模块(11包含)   例如:mmm dir/:target1,target2.
mma 编译当前目录下的源码,包含他们的依赖模块
mmma 编译指定目录下的所模块,包含他们的依赖模块
provision 具有所有必需分区的闪存设备。选项将传递给fastboot。
cgrep 对系统本地所有的C/C++ 文件执行grep命令
ggrep 对系统本地所有的Gradle文件执行grep命令
jgrep 对系统本地所有的Java文件执行grep命令
resgrep 对系统本地所有的res目录下的xml文件执行grep命令
mangrep 对系统本地所有的AndroidManifest.xml文件执行grep命令
mgrep 对系统本地所有的Makefiles文件执行grep命令
sepgrep 对系统本地所有的sepolicy文件执行grep命令
sgrep 对系统本地所有的source文件执行grep命令
godir 根据godir后的参数文件名在整个目录下查找,并且切换目录
allmod 列出所有模块
gomod 转到包含模块的目录
pathmod 获取包含模块的目录
refreshmod 刷新allmod/gomod的模块列表

2.2 validate_current_shell

确定当前的shell环境,建立shell命令

function validate_current_shell() {
    local current_sh="$(ps -o command -p $$)"
    case "$current_sh" in
        *bash*)
            function check_type() { type -t "$1"; }
            ;;
        *zsh*)
            function check_type() { type "$1"; }
            enable_zsh_completion ;;
        *)
            echo -e "WARNING: Only bash and zsh are supported.\nUse of other shell would lead to erroneous results."
            ;;
    esac
}

2.3 source_vendorsetup

从device\vendor\product等目录遍历搜索vendorsetup.sh,并source进来

function source_vendorsetup() {
    allowed=
    for f in $(find -L device vendor product -maxdepth 4 -name 'allowed-vendorsetup_sh-files' 2>/dev/null | sort); do
        if [ -n "$allowed" ]; then
            echo "More than one 'allowed_vendorsetup_sh-files' file found, not including any vendorsetup.sh files:"
            echo "  $allowed"
            echo "  $f"
            return
        fi
        allowed="$f"
    done

    allowed_files=
    [ -n "$allowed" ] && allowed_files=$(cat "$allowed")
    for dir in device vendor product; do
        for f in $(test -d $dir && \
            find -L $dir -maxdepth 4 -name 'vendorsetup.sh' 2>/dev/null | sort); do

            if [[ -z "$allowed" || "$allowed_files" =~ $f ]]; then
                echo "including $f"; . "$f"
            else
                echo "ignoring $f, not in $allowed"
            fi
        done
    done
}

例:

1.建立一个目录:/vendor/ingres/build

2.创建一个vendorsetup.sh

写一个log: echo "vendor build test."

3.执行source build/envsetup.sh

source后打印:

including vendor/ingres/build/vendorsetup.sh
vendor build test.

2.4 addcompletions

function addcompletions()
{
    local T dir f

    # Keep us from trying to run in something that's neither bash nor zsh.
        # 检测shell版本字符串BASH_VERSION 或ZSH_VERSION长度为0时,返回
    if [ -z "$BASH_VERSION" -a -z "$ZSH_VERSION" ]; then
        return
    fi

    # Keep us from trying to run in bash that's too old.
    # 检测bash主版本低于3时返回
    if [ -n "$BASH_VERSION" -a ${BASH_VERSINFO[0]} -lt 3 ]; then
        return
    fi

        # 指定bash文件目录并检查是否存在
    local completion_files=(
      system/core/adb/adb.bash
      system/core/fastboot/fastboot.bash
      tools/asuite/asuite.sh
    )
    # Completion can be disabled selectively to allow users to use non-standard completion.
    # e.g.
    # ENVSETUP_NO_COMPLETION=adb # -> disable adb completion
    # ENVSETUP_NO_COMPLETION=adb:bit # -> disable adb and bit completion
        #*.bash文件列表,并将这些*.bash文件包含进来
    for f in ${completion_files[*]}; do
        if [ -f "$f" ] && should_add_completion "$f"; then
                 # 对*.bash文件执行'.'操作
            . $f
        fi
    done

    if should_add_completion bit ; then
        complete -C "bit --tab" bit
    fi
    if [ -z "$ZSH_VERSION" ]; then
        # Doesn't work in zsh.
        complete -o nospace -F _croot croot
    fi
    complete -F _lunch lunch   # _lunch命令提供lunch命令的补全操作

    complete -F _complete_android_module_names gomod
    complete -F _complete_android_module_names m
}

3. lunch aosp_arm_eng

3.1 lunch说明

环境变量初始化完成后,我们需要选择一个编译目标。lunch 主要作用是根据用户输入或者选择的产品名来设置与具体产品相关的环境变量。

执行命令:lunch 1, 可以看到配置的一些环境变量
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_.jpg

lunch结果 说明
PLATFORM_VERSION_CODENAME=REL 表示平台版本的名称
PLATFORM_VERSION=10 Android平台的版本号
TARGET_PRODUCT=aosp_arm 所编译的产品名称
TARGET_BUILD_VARIANT=userdebug 所编译产品的类型
TARGET_BUILD_TYPE=release 编译的类型,debug和release
TARGET_ARCH=arm 表示编译目标的CPU架构
TARGET_ARCH_VARIANT=armv7-a-neon 表示编译目标的CPU架构版本
TARGET_CPU_VARIANT=generic 表示编译目标的CPU代号
HOST_ARCH=x86_64 表示编译平台的架构
HOST_2ND_ARCH=x86 表示编译平台的第二CPU架构
HOST_OS=linux 表示编译平台的操作系统
HOST_OS_EXTRA=Linux-4.15.0-112-generic-x86_64-Ubuntu-16.04.6-LTS 编译系统之外的额外信息
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release 编译类型
BUILD_ID=QQ1D.200205.002 BUILD_ID会出现在版本信息中,可以利用
OUT_DIR=out 编译结果输出的路径

lunch aosp_arm-eng 结束后,后创建一个out文件夹,生成一些中间文件如下图所示:
image3570d74126471700.png

3.2 lunch()

lunch命令用来设置 TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_PLATFORM_VERSION、TARGET_BUILD_TYPE、TARGET_BUILD_APPS等环境变量

lunch操作流程如下:
1.获取lunch操作的参数,如果参数不为空,参数则为指定要编译的设备型号和编译类型;如果参数为空,会调用print_lunch_menu来显示Lunch菜单项,读取用户的输入,存入answer

2.如果answer为空,即之前在lunch菜单用,用户只敲了一个回车。会将默认选项改为aosp_arm-eng,结果存入selection

3.如果lunch操作得到的输入是数字,则将数字转换为LUNCH_MENU_CHOICES中的字符串,结果存入selection

4.解析selection的值,得到product = aosp_arm 和variant = eng, 把他们分别保存到TARGET_PRODUCT 和 TARGET_BUILD_VARIANT 中

5.根据前面的设置,调用build_build_var_cache 来更新编译环境相关变量

6.export 编译选项TARGET_PRODUCT, TARGET_BUILD_VARIANT和TARGET_BUILD_TYPE三元组

7.调用set_stuff_for_environment 来设置其他环境变量,如PROMPT_COMMAND,编译toolchain和tools相关的路径等

8.调用printconfig 来输出当前的设置选项

function lunch()
{
    local answer
        # 获取lunch操作的参数
    if [ "$1" ] ; then
        answer=$1
    else
           # lunch操作不带参数,则先显示lunch menu,然后读取用户输入
        print_lunch_menu
        echo -n "Which would you like? [aosp_arm-eng] "
        read answer
    fi

    local selection=
       # lunch操作得到的结果为空(例如用户直接在lunch要求输入时回车的情况)
    # 则将选项默认为"aosp_arm-eng"
    if [ -z "$answer" ]
    then
        selection=aosp_arm-eng
        # lunch操作得到的输入是数字,则将数字转换为LUNCH_MENU_CHOICES中的字符串
    elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
    then
        local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))
        if [ $answer -le ${#choices[@]} ]
        then
            # array in zsh starts from 1 instead of 0.
            if [ -n "$ZSH_VERSION" ]
            then
                selection=${choices[$(($answer))]}
            else
                selection=${choices[$(($answer-1))]}
            fi
        fi
    else
        selection=$answer
    fi

    export TARGET_BUILD_APPS=

    local product variant_and_version variant version

    product=${selection%%-*} # Trim everything after first dash
    variant_and_version=${selection#*-} # Trim everything up to first dash
    if [ "$variant_and_version" != "$selection" ]; then
        variant=${variant_and_version%%-*}
        if [ "$variant" != "$variant_and_version" ]; then
            version=${variant_and_version#*-}
        fi
    fi

    if [ -z "$product" ]
    then
        echo
        echo "Invalid lunch combo: $selection"
        return 1
    fi

        # 设置TARGET_PRODUCT和TARGET_BUILD_VARIANT
    TARGET_PRODUCT=$product \
    TARGET_BUILD_VARIANT=$variant \
    TARGET_PLATFORM_VERSION=$version \

    # 根据前面的设置,更新编译环境相关变量
    build_build_var_cache     #参考[3.1.1]
    if [ $? -ne 0 ]
    then
        return 1
    fi

        # export 编译选项TARGET_PRODUCT, TARGET_BUILD_VARIANT和TARGET_BUILD_TYPE三元组
    export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
    export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
    if [ -n "$version" ]; then
      export TARGET_PLATFORM_VERSION=$(get_build_var TARGET_PLATFORM_VERSION)
    else
      unset TARGET_PLATFORM_VERSION
    fi
    export TARGET_BUILD_TYPE=release

    echo

    set_stuff_for_environment # 设置其他环境变量,如PROMPT_COMMAND,编译toolchain和tools相关的路径等
    printconfig        # 输出当前的设置选项
    destroy_build_var_cache
}

3.1.1 build_build_var_cache()

根据前面的设置,更新编译环境相关变量

主要通过执行 "build/soong/soong_ui.bash --dumpvars-mode" 完成

最终执行的是 "./out/soog_ui --dumpvars-mode"

function build_build_var_cache()
{
    local T=$(gettop)
    # Grep out the variable names from the script.
    cached_vars=(`cat $T/build/envsetup.sh | tr '()' '  ' | awk '{for(i=1;i<=NF;i++) if($i~/get_build_var/) print $(i+1)}' | sort -u | tr '\n' ' '`)
    cached_abs_vars=(`cat $T/build/envsetup.sh | tr '()' '  ' | awk '{for(i=1;i<=NF;i++) if($i~/get_abs_build_var/) print $(i+1)}' | sort -u | tr '\n' ' '`)
    # Call the build system to dump the "<val>=<value>" pairs as a shell script.
    build_dicts_script=`\builtin cd $T; build/soong/soong_ui.bash --dumpvars-mode \
                        --vars="${cached_vars[*]}" \
                        --abs-vars="${cached_abs_vars[*]}" \
                        --var-prefix=var_cache_ \
                        --abs-var-prefix=abs_var_cache_`
    local ret=$?
    if [ $ret -ne 0 ]
    then
        unset build_dicts_script
        return $ret
    fi
    # Execute the script to store the "<val>=<value>" pairs as shell variables.
    eval "$build_dicts_script"
    ret=$?
    unset build_dicts_script
    if [ $ret -ne 0 ]
    then
        return $ret
    fi
    BUILD_VAR_CACHE_READY="true"
}

soong_ui 由build/soong/cmd/soong_ui/main.go编译生成

[build/soong/cmd/soong_ui/main.go]
func main() {
...
    if os.Args[1] == "--dumpvar-mode" {
        dumpVar(buildCtx, config, os.Args[2:])
    } else if os.Args[1] == "--dumpvars-mode" {
        dumpVars(buildCtx, config, os.Args[2:])
    } else {
        ...
    }
...
}
[build/soong/cmd/soong_ui/main.go]
func dumpVars(ctx build.Context, config build.Config, args []string) {
       varData, err := build.DumpMakeVars(ctx, config, nil, allVars)
}

最后调用到了ckati执行-f build/make/core/config.mk

[/build/soong/ui/build/dumpvars.go]
func dumpMakeVars(ctx Context, config Config, goals, vars []string, write_soong_vars bool) (map[string]string, error) {
    ctx.BeginTrace(metrics.RunKati, "dumpvars")
    defer ctx.EndTrace()

    cmd := Command(ctx, config, "dumpvars",
        config.PrebuiltBuildTool("ckati"),
        "-f", "build/make/core/config.mk",
        "--color_warnings",
        "--kati_stats",
        "dump-many-vars",
        "MAKECMDGOALS="+strings.Join(goals, " "))
    cmd.Environment.Set("CALLED_FROM_SETUP", "true")
    if write_soong_vars {
        cmd.Environment.Set("WRITE_SOONG_VARIABLES", "true")
    }
    cmd.Environment.Set("DUMP_MANY_VARS", strings.Join(vars, " "))
    cmd.Sandbox = dumpvarsSandbox
    output := bytes.Buffer{}
    cmd.Stdout = &output
    pipe, err := cmd.StderrPipe()
    if err != nil {
        ctx.Fatalln("Error getting output pipe for ckati:", err)
    }
    cmd.StartOrFatal()
    // TODO: error out when Stderr contains any content
    status.KatiReader(ctx.Status.StartTool(), pipe)
    cmd.WaitOrFatal()

    ret := make(map[string]string, len(vars))
    ...

    return ret, nil
}

下面我们单独研究一下config.mk

4. config.mk

image3a1c4dccfacfbe03.png
 说明:config.mk首先加载了build/make/common 中的core.mk、math.mk、strings.mk、json.mk 用来配置一些shell环境、math函数、string和json的一些支持函数。最主要的操作还是加载build/make/core中的envsetup.mk和dumpvar.mk

    ...

#配置两个目录的变量,供之后的mk使用
BUILD_SYSTEM :=$= build/make/core
BUILD_SYSTEM_COMMON :=$= build/make/common

#加载core.mk, 只使用ANDROID_BUILD_SHELL来包装bash。
include $(BUILD_SYSTEM_COMMON)/core.mk


#设置make中使用的有效数学函数。
include $(BUILD_SYSTEM_COMMON)/math.mk

include $(BUILD_SYSTEM_COMMON)/strings.mk

include $(BUILD_SYSTEM_COMMON)/json.mk

# 避免硬件解码路径被覆盖的调用pathmap.mk建立硬解映射
include $(BUILD_SYSTEM)/pathmap.mk

# 允许项目定义自己的全局可用变量
include $(BUILD_SYSTEM)/project_definitions.mk


# ###############################################################
# Build system internal files
# ###############################################################
# 构建系统内部文件(写Android.mk时会调用include头文件,也就是这些makefile文件)

BUILD_COMBOS:= $(BUILD_SYSTEM)/combo

CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk
BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk

...

BUILD_NOTICE_FILE := $(BUILD_SYSTEM)/notice_files.mk
BUILD_HOST_DALVIK_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_java_library.mk
BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_static_java_library.mk

BUILD_HOST_TEST_CONFIG := $(BUILD_SYSTEM)/host_test_config.mk
BUILD_TARGET_TEST_CONFIG := $(BUILD_SYSTEM)/target_test_config.mk



#定义大多数全局变量。这些是特定于用户的构建配置的。
include $(BUILD_SYSTEM)/envsetup.mk

#构建系统为在哪里找到内核公开了几个变量
#(1)TARGET_DEVICE_KERNEL_HEADERS是为当前正在构建的设备自动创建的。
#它被设置为$(TARGET_DEVICE_DIR)/kernel headers,
#例如DEVICE/samsung/tuna/kernel headers。此目录不是由任何人显式设置的,生成系统总是添加此子目录。
TARGET_DEVICE_KERNEL_HEADERS := $(strip $(wildcard $(TARGET_DEVICE_DIR)/kernel-headers))

#(2)TARGET_BOARD_KERNEL_HEADERS由BoardConfig.mk允许包含其他目录的文件。
#如果有一些常见的地方为一组设备保留了一些报头,那么这很有用。
#例如,device/<vendor>/common/kernel头可以包含一些<vendor>设备的头。
TARGET_BOARD_KERNEL_HEADERS := $(strip $(wildcard $(TARGET_BOARD_KERNEL_HEADERS)))
TARGET_BOARD_KERNEL_HEADERS := $(patsubst %/,%,$(TARGET_BOARD_KERNEL_HEADERS))
$(call validate-kernel-headers,$(TARGET_BOARD_KERNEL_HEADERS))

#(3)TARGET_PRODUCT_KERNEL_头由产品继承图生成。
#这允许体系结构产品为使用该体系结构的设备提供报头。
TARGET_PRODUCT_KERNEL_HEADERS := $(strip $(wildcard $(PRODUCT_VENDOR_KERNEL_HEADERS)))
TARGET_PRODUCT_KERNEL_HEADERS := $(patsubst %/,%,$(TARGET_PRODUCT_KERNEL_HEADERS))
$(call validate-kernel-headers,$(TARGET_PRODUCT_KERNEL_HEADERS))


# 选择一个Java编译器
include $(BUILD_SYSTEM)/combo/javac.mk

# A list of SEPolicy versions, besides PLATFORM_SEPOLICY_VERSION, that the framework supports.
#框架支持的SEPolicy版本列表,除了PLATFORM_SEPOLICY_VERSION
PLATFORM_SEPOLICY_COMPAT_VERSIONS := \
    26.0 \
    27.0 \
    28.0 \

ifeq ($(CALLED_FROM_SETUP),true)
include $(BUILD_SYSTEM)/ninja_config.mk
include $(BUILD_SYSTEM)/soong_config.mk
endif

#加载dumpvar.mk,用来生成make目标
include $(BUILD_SYSTEM)/dumpvar.mk

4.1 build/make/core/envsetup.mk

envsetup.mk 主要加载了product_config.mk和board_config.mk,用来得到TARGET_DEVICE和其他变量。

...
#设置host和target编译链相关的变量
include $(BUILD_SYSTEM)/combo/select.mk
#(1)阅读产品规格,这样我们就可以得到TARGET_DEVICE和其他变量,我们需要找到输出文件
include $(BUILD_SYSTEM)/product_config.mk

include $(BUILD_SYSTEM)/board_config.mk
...

4.2 build/make/core/product_config.mk

阅读产品规格,这样我们就可以得到TARGET_DEVICE和其他变量,我们需要找到输出文件。

...
# ---------------------------------------------------------------
# Include the product definitions.
# We need to do this to translate TARGET_PRODUCT into its
# underlying TARGET_DEVICE before we start defining any rules.
#
include $(BUILD_SYSTEM)/node_fns.mk
include $(BUILD_SYSTEM)/product.mk
include $(BUILD_SYSTEM)/device.mk

...

#############################################################################
# Sanity check and assign default values
TARGET_DEVICE := $(PRODUCT_DEVICE)
...

4.3 build/make/core/board_config.mk

板级可以在$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)下定义,也可以在vendor/\*/$(TARGET_DEVICE)下定义。
在这两个地方搜索,但要确保只存在一个。真正的板级应始终与OEM vendor相关联。

...
# Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)
# or under vendor/*/$(TARGET_DEVICE).  Search in both places, but
# make sure only one exists.
# Real boards should always be associated with an OEM vendor.
ifdef TARGET_DEVICE_DIR
  ifneq ($(origin TARGET_DEVICE_DIR),command line)
    $(error TARGET_DEVICE_DIR may not be set manually)
  endif
  board_config_mk := $(TARGET_DEVICE_DIR)/BoardConfig.mk
else
  board_config_mk := \
    $(strip $(sort $(wildcard \
      $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
      $(shell test -d device && find -L device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
      $(shell test -d vendor && find -L vendor -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
    )))
  ifeq ($(board_config_mk),)
    $(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))
  endif
  ifneq ($(words $(board_config_mk)),1)
    $(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))
  endif
  TARGET_DEVICE_DIR := $(patsubst %/,%,$(dir $(board_config_mk)))
  .KATI_READONLY := TARGET_DEVICE_DIR
endif
include $(board_config_mk)
...

5. 总结

至此,envsetup.sh 和lunch()的初始化流程基本上理清了,主要就是加载了环境变量,并选择了编译目标,后面只要执行一下make就能够进行启动编译,下一节让我们一起看看敲下make后到底发生了什么。


[转载]编译系统入门篇-Android10.0编译系统(一)

0 条评论 Android 无标签 adtxl

转载自编译系统入门篇-Android10.0编译系统(一)
版权声明:本文为CSDN博主「IngresGe」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yiranfeng/article/details/109082489

1. 概述

在 Android 7.0 之前,Android 编译系统使用 GNU Make 描述和shell来构建编译规则,模块定义都使用Android.mk进行定义,Android.mk的本质就是Makefile,但是随着Android的工程越来越大,模块越来越多,Makefile组织的项目编译时间越来越长。这样下去Google工程师觉得不行,得要优化。

因此,在Android7.0开始,Google采用ninja来代取代之前使用的make,由于之前的Android.mk数据实在巨大,因此Google加入了一个kati工具,用于将Android.mk转换成ninja的构建规则文件buildxxx.ninja,再使用ninja来进行构建工作。

ninja的网址:https://ninja-build.org

编译速度快了一些,但是既然要干, 那就干个大的,最终目标要把make都取代,于是从Android8.0开始,Google为了进一步淘汰Makefile,因此引入了Android.bp文件来替换之前的Android.mk。

Android.bp只是一个纯粹的配置文件,不包括分支、循环语句等控制流程,本质上就是一个json配置文件。Android.bp  通过Blueprint+soong转换成ninja的构建规则文件build.ninja,再使用ninja来进行构建工作。

Android10.0上,mk和bp编译的列表可以从 \out.module_paths中的Android.bp.list、Android.mk.list中看到,Android10.0还有400多个mk文件没有被替换完,Google任重道远。

Android编译演进过程:

  • Android7.0之前 使用GNU Make
  • Android7.0 引入ninja、kati、Android.bp和soong构建系统
  • Android8.0 默认打开Android.bp
  • Android9.0 强制使用Android.bp

Google在 Android 7.0之后,引入了Soong构建系统,旨在取代make,它利用 Kati GNU Make 克隆工具和 Ninja 构建系统组件来加速 Android 的构建。

Make 构建系统得到了广泛的支持和使用,但在 Android 层面变得缓慢、容易出错、无法扩展且难以测试。Soong 构建系统正好提供了 Android build 所需的灵活性。

Android系统的编译历程:
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-4.jpg

2. 编译流程

2.1 编译构成

Android的编译目录在/build 中,看一下Android 10源码中的build目录,现在是这个样子:
image.png
这个目录中可以看到core文件夹被link到了make/core,envsetup.sh被link到make/envsetup.sh,这主要是为了对使用者屏蔽切换编译系统的差异。

这里重点看四个文件夹:blueprint、kati、make、soong

  • blueprint:用于处理Android.bp,编译生成*.ninja文件,用于做ninja的处理
  • kati:用于处理Android.mk,编译生成*.ninja文件,用于做ninja的处理
  • make:文件夹还是原始的make那一套流程,比如envsetup.sh
  • soong:构建系统,核心编译为soong_ui.bash

Soong编译系统家族成员及各自关系如下图所示:
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-1.jpg

在编译过程中,Android.bp会被收集到out/soong/build.ninja.d,blueprint以此为基础,生成out/soong/build.ninja

Android.mk会由kati/ckati生成为out/build-aosp_arm.ninja

两个ninja文件会被整合进入out/combined-aosp_arm.ninja

out/combined-aosp_arm.ninja内容如下所示:

builddir = out
pool local_pool
 depth = 42
build _kati_always_build_: phony
subninja out/build-aosp_arm.ninja
subninja out/build-aosp_arm-package.ninja
subninja out/soong/build.ninja

2.2 编译步骤

source build/envsetup.sh

lunch aosp_arm-eng // 或者 m PRODUCT-aosp_x86_64-eng ,Android10.0不一定需要lunch命令

make -j8      //编译模块也可以直接用 m libart

Android10.0编译步骤如下图所示:
20201014202755574.jpg

3. 编译环境初始化

3.1 envsetup说明

 source build/envsetup.sh

这里的envsetup.sh被link到了 build/make/envsetup.sh

envsetup.sh 主要做了下面几个事情:
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-3.jpg
在source build/envsetup.sh后,输入hmm可以看到envsetup支持的一些接口:

命令 说明
lunch lunch <product_name>-<build_variant>选择<product_name>作为要构建的产品,<build_variant>作为要构建的变体,并将这些选择存储在环境中,以便后续调用“m”等读取。
tapas 交互方式:tapas [<App1> <App2> ...] [arm\x86\mips\arm64\x86_64\mips64] [eng\userdebug\user]
croot 将目录更改到树的顶部或其子目录。
m 编译整个源码,可以不用切换到根目录
mm 编译当前目录下的源码,不包含他们的依赖模块
mmm 编译指定目录下的所有模块,不包含他们的依赖模块   例如:mmm dir/:target1,target2.
mma 编译当前目录下的源码,包含他们的依赖模块
mmma 编译指定目录下的所模块,包含他们的依赖模块
provision 具有所有必需分区的闪存设备。选项将传递给fastboot。
cgrep 对系统本地所有的C/C++ 文件执行grep命令
ggrep 对系统本地所有的Gradle文件执行grep命令
jgrep 对系统本地所有的Java文件执行grep命令
resgrep 对系统本地所有的res目录下的xml文件执行grep命令
mangrep 对系统本地所有的AndroidManifest.xml文件执行grep命令
mgrep 对系统本地所有的Makefiles文件执行grep命令
sepgrep 对系统本地所有的sepolicy文件执行grep命令
sgrep 对系统本地所有的source文件执行grep命令
godir 根据godir后的参数文件名在整个目录下查找,并且切换目录
allmod 列出所有模块
gomod 转到包含模块的目录
pathmod 获取包含模块的目录
refreshmod 刷新allmod/gomod的模块列表

3.2 Lunch说明

环境变量初始化完成后,我们需要选择一个编译目标。lunch 主要作用是根据用户输入或者选择的产品名来设置与具体产品相关的环境变量。
如果你不知道想要编译的目标是什么,直接执行一个lunch命令,会列出所有的目标,直接回车,会默认使用aosp_arm-eng这个目标。
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-6.jpg
执行命令:lunch 1, 可以看到配置的一些环境变量
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_.jpg
这些环境变量的含义如下:

lunch结果 说明
PLATFORM_VERSION_CODENAME=REL 表示平台版本的名称
PLATFORM_VERSION=10 Android平台的版本号
TARGET_PRODUCT=aosp_arm 所编译的产品名称
TARGET_BUILD_VARIANT=userdebug 所编译产品的类型
TARGET_BUILD_TYPE=release 编译的类型,debug和release
TARGET_ARCH=arm 表示编译目标的CPU架构
TARGET_ARCH_VARIANT=armv7-a-neon 表示编译目标的CPU架构版本
TARGET_CPU_VARIANT=generic 表示编译目标的CPU代号
HOST_ARCH=x86_64 表示编译平台的架构
HOST_2ND_ARCH=x86 表示编译平台的第二CPU架构
HOST_OS=linux 表示编译平台的操作系统
HOST_OS_EXTRA=Linux-4.15.0-112-generic-x86_64-Ubuntu-16.04.6-LTS 编译系统之外的额外信息
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release 编译类型
BUILD_ID=QQ1D.200205.002 BUILD_ID会出现在版本信息中,可以利用
OUT_DIR=out 编译结果输出的路径

4. Make说明

执行完lunch命令后,就可以使用make命令来执行编译Build。

Android10.0上是通过soong执行编译构建,这里执行make命令时,main.mk文件把一些环境变量和目标都配置好后,会执行envsetup.sh中的make()进行编译。

如果找到“build/soong/soong_ui.bash”,就使用soong_ui.bash 来进行编译,否则使用原始的make命令进行编译。

function make()
{
    _wrap_build $(get_make_command "$@") "$@"
}
function get_make_command()
{
    # If we're in the top of an Android tree, use soong_ui.bash instead of make
    if [ -f build/soong/soong_ui.bash ]; then
        # Always use the real make if -C is passed in
        for arg in "$@"; do
            if [[ $arg == -C* ]]; then
                echo command make
                return
            fi
        done
        echo build/soong/soong_ui.bash --make-mode
    else
        echo command make
    fi
}

配置一些资源环境,得到一些函数命令,例如:soong_build_go,最终回退到根目录,执行out/soong_ui --make-mode进行真正的构建

soong_build_go soong_ui android/soong/cmd/soong_ui  是通过编译

android/soong/cmd/soong_ui/main.go来编译生成soong_ui。

[build/soong/soong_ui.bash]
# Save the current PWD for use in soong_ui
export ORIGINAL_PWD=${PWD}
export TOP=$(gettop)
source ${TOP}/build/soong/scripts/microfactory.bash

soong_build_go soong_ui android/soong/cmd/soong_ui

cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"

“echo build/soong/soong_ui.bash --make-mode ”

最终会执行 exec out/soong_ui --make-mode 进行编译

soong的编译过程如下图所示:
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-5.jpg

执行runKatiBuild时,有个重要的步骤,就是加载build/make/core/main.mk,main.mk文件是Android Build系统的主控文件。从main.mk开始,将通过include命令将其所有需要的.mk文件包含进来,最终在内存中形成一个包括所有编译脚本的集合,这个相当于一个巨大Makefile文件。Makefile文件看上去很庞大,其实主要由三种内容构成: 变量定义、函数定义和目标依赖规则,此外mk文件之间的包含也很重要。
main.mk的包含关系如下图所示:
watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lpcmFuZmVuZw___size_-2.jpg
一些关键的mk文件说明:

文件 说明
build/make/core/main.mk Build的主控文件,主要作用是包含其他mk,以及定义几个最重要的编译目标,同时检查编译工具的版本,例如如gcc、clang、java等
build/make/core/config.mk Build的配置文件,主要是区分各个产品的配置,并将这些编译器参数引入产品配置 BoardConfig.mk,同时也配置了一些编译器的路径等
build/make/core/clang/config.mk clang编译的配置文件
build/make/core/definitions.mk 最重要的 Make 文件之一,在其中定义了大量的函数。这些函数都是 Build 系统的其他文件将用到的。例如:my-dir,all-subdir-makefiles,find-subdir-files,sign-package 等,关于这些函数的说明请参见每个函数的代码注释。
build/make/core/dex_preopt.mk 定义了dex优化相关的路径和参数
build/make/core/pdk_config.mk 编译pdk的配置文件
build/make/core/Makefile 系统最终编译完成所需要的各种目标和规则
build/make/core/envsetup.mk 包含进product_config.mk文件并且根据其内容设置编译产品所需要的环境变量,并检查合法性,指定输出路径等
build/make/core/combo/select.mk 根据当前编译器的平台选择平台相关的 Make 文件
build/make/core/ninja_config.mk 解析makefile的的列表,传给kati,配置传给ninja和kati的目标
build/make/core/soong_config.mk 配置soong的环境变量,建立go变量和mk变量的json映射关系,让go变量可以获取到mk中定义的变量值

5. 编译工具链说明

Android10.0的编译系统中,涉及以下一些工具链,由这些工具链相辅相成,才最终编译出了我们所需要的镜像版本。
Android10.0编译工具链:

soong\kati\blueprint\ninja 

5.1 Soong说明

Soong 构建系统是在 Android 7.0 (Nougat) 中引入的,旨在取代 Make。它利用 Kati GNU Make 克隆工具和 Ninja 构建系统组件来加速 Android 的构建。

Soong是由Go语言写的一个项目,从Android 7.0开始,在prebuilts/go/目录下新增了Go语言所需的运行环境,Soong在编译时使用,解析Android.bp,将之转化为Ninja文件,完成Android的选择编译,解析配置工作等。故Soong相当于Makefile编译系统的核心,即build/make/core下面的内容。

另外Soong还会编译产生一个androidmk命令,可以用来手动将Android.mk转换成Android.bp文件。不过这只对无选择、循环等复杂流程控制的Android.mk生效。

soong脚本和代码目录:/build/soong

5.2 kati说明

kati是一个基于Makefile来生成ninja.build的小项目。主要用于把Makefiel转成成ninja file,自身没有编译能力,转换后使用Ninja编译。

在编译过程中,kati负责把既有的Makefile、Android.mk文件,转换成Ninja文件。在Android 8.0以后,它与Soong一起,成为Ninja文件的两大来源。Kati更像是Google过渡使用的一个工具,等所有Android.mk都被替换成Android.bp之后,Kati有可能退出Android编译过程.

在单独使用时,它对普通的小项目还能勉强生效。面对复杂的、多嵌套的Makefile时,它往往无法支持,会出现各种各样的问题。当然,也可以理解为,它只为Android而设计。

kati脚本和代码目录:/build/kati

5.3 blueprint说明

Blueprint由Go语言编写,是生成、解析Android.bp的工具,是Soong的一部分。Soong则是专为Android编译而设计的工具,Blueprint只是解析文件的形式,而Soong则解释内容的含义。

在Android编译最开始的准备阶段,会执行build/soong/soong_ui.bash进行环境准备。 

对blueprint项目编译完成之后会在out/soong/host/linux-x86/bin目录下生成soong编译需要的5个执行文件(bpfix,bpfmt,bpmodify,microfatory,bpmodify)。

Soong是与Android强关联的一个项目,而Blueprint则相对比较独立,可以单独编译、使用。

blueprint代码目录:/build/blueprint

5.4 ninja说明

最开始,Ninja 是用于Chromium 浏览器中,Android 在SDK 7.0 中也引入了Ninja。

Ninja是一个致力于速度的小型编译系统(类似于Make),如果把其他编译系统比做高级语言的话,Ninja就是汇编语言。通常使用Kati或soong把makefile转换成Ninja files,然后用Ninja编译。

主要两个特点:

1)可以通过其他高级的编译系统生成其输入文件;
2)它的设计就是为了更快的编译;

ninja核心是由C/C++编写的,同时有一部分辅助功能由python和shell实现。由于其开源性,所以可以利用ninja的开源代码进行各种个性化的编译定制。

从Android 7开始,编译时默认使用Ninja。但是,Android项目里是没有.ninja文件的。遵循Ninja的设计哲学,编译时,会先把Makefile通过kati转换成.ninja文件,然后使用ninja命令进行编译。这些.ninja文件,都产生在out/目录下,共有三类:

第一类是build-*.ninja文件,通常非常大,几十到几百MB。对make全编译,命名是build-<product_name>.ninja。如果Makefile发生修改,需要重新产生Ninja文件。

mm、mma的Ninja文件,命名是build-<product_name>-<path_to_Android.mk>.ninja。而mmm、mmma的Ninja文件,命名是build-<product_name>-_<path_to_Android.mk>.ninja

第二类是combined-*.ninja文件。在使用了Soong后,除了build-*.ninja之外,还会产生对应的combined-*.ninja,二者的*内容相同。

这类是组合文件,是把build-*.ninja和out/soong/build.ninja组合起来。所以,使用Soong后,combined-*.ninja是编译执行的真正入口。

第三类是out/soong/build.ninja文件,它是从所有的Android.bp转换过来的。

build-*.ninja是从所有的Makefile,用Kati转换过来的,包括build/core/*.mk和所有的Android.mk。所以,在不使用Soong时,它是唯一入口。在使用了Soong以后,会新增源于Android.bp的out/soong/build.ninja,所以需要combined-*.ninja来组合一下。

6. 工具链

Android.mk文件、Android.bp、kati、Soong、Blueprint、Ninja之间的关系如下:

Android.bp --> Blueprint --> Soong --> Ninja 
  Makefile or Android.mk --> kati --> Ninja 
  (Android.mk --> Soong --> Blueprint --> Android.bp)

Blueprint是生成、解析Android.bp的工具,是Soong的一部分。Soong则是专为Android编译而设计的工具,Blueprint只是解析文件的形式,而Soong则解释内容的含义。

  Android.mk可以通过Soong提供的androidmk转换成Android.bp,但仅限简单配置。目前Oreo的编译流程中,仍然是使用kati来做的转换。

  现存的Android.mk文件、既有的Android.bp,都会分别被转换成Ninja。从Android.mk与其它Makefile,会生成out/build-<product_name>.ninja文件。而从Android.bp,则会生成out/soong/build.ninja。此外,还会生成一个较小的out/combined-<product_name>.ninja文件,负责把二者组合起来,作为执行入口。

最终,Ninja文件才是真正直接控制源码编译的工具。

7. 总结

Android10.0中,mk文件通过kati\ckati编译生成 build-aosp_arm.ninja, bp文件通过blueprint-soong解析编译生成为build.ninja ,这些ninja文件会合并成combined-aosp_arm.ninja,最终通过ninja工具进行最终的编译。

随着Google的不停演进,make的编译会最终退出历史舞台,kati\ckati也会退出,最终全部切到 blueprint-soong的编译。


Android init.rc文件

0 条评论 Android 无标签 adtxl

1. 文件简介

Android init.rc文件由系统第一个启动的init程序解析,此文件由语句组成,主要包含了四种类型的语句:Action,Commands,Services,Options.在init.rc文件中一条语句通常是占据一行.单词之间是通过空格符来相隔的.如果需要在单词内使用空格,那么得使用转义字符”\”,如果在一行的末尾有一个反斜杠,那么是换行折叠符号,应该和下一行合并成一起来处理,这样做主要是为了避免一行的字符太长,与C语言中的含义是一致的。注释是以#号开头。 Action和services显式声明了一个语句块,而commands和options属于最近声明的语句块。在第一个语句块之前 的commands和options会被忽略.

init.rc:Android在启动过程中读取的启动脚本文件,主要完成一些初级的初始化,在/system/core/init/init.cpp中解析。rc 经常被用作程序之启动脚本的文件名。它是“run commands”(运行命令)的缩写。

init.xx.rc:与具体CPU相关的启动脚本,比如对于MTK的CPU,名字为init.usb.rc,init.trace.rc。在init.rc之后得到解析。

厂商的rc文件一般位于device目录

2.init.rc和init.xx.rc文件的修改

我们以 init.rc 来入手,学习 rc 的用法。

2.1 文件结构

init.rc 基本单位是 section(语句块)。一个Section以Service或On开头的语句块.以Service开头的Section叫做服务,而以On开头的叫做动作(Action).

services: 服务.
Action: 动作
commands:命令.
options:选项.
trigger:触发器,或者叫做触发条件.
class: 类属,即可以为多个service指定一个相同的类属,方便操作同时启动或停止.

section 有三种类型:

  1. on
  2. service
  3. import

on 类型

作表示了一组命令(commands)组成.动作包含一个触发器,决定了何时执行这个动作。当触发器的条件满足时,这个动作会被加入到已被执行的队列尾。如果此动作在队列中已经存在,那么它将不会执行.
一个动作所包含的命令将被依次执行。动作的语法如下所示:

on <trigger>
    <command>
    <command>
    ...

on 类型 表示一系列的命令组合,eg:

on init
    # See storage config details at http://source.android.com/tech/storage/
    #mkdir /storage/sdcard 0555 root root

    #export EXTERNAL_STORAGE /storage/sdcard

    # Support legacy paths
    #symlink /storage/sdcard /sdcard
    #symlink /storage/sdcard /mnt/sdcard

    # mkdir /tmp 01775 root root
    # mount tmpfs tmpfs /tmp size=25m

    mkdir /dev/cpuctl/bg_non_interactive
    chown system system /dev/cpuctl/bg_non_interactive/tasks
    chmod 0666 /dev/cpuctl/bg_non_interactive/tasks

    # KSM config
    write /sys/kernel/mm/ksm/pages_to_scan 100
    write /sys/kernel/mm/ksm/sleep_millisecs 500
    write /sys/kernel/mm/ksm/run 1

    write /sys/block/zram0/comp_algorithm lz4
    write /sys/block/zram0/max_comp_streams 2

    # Swap in only 1 page at a time
    write /proc/sys/vm/page-cluster 0

这样一个 section 里面包含了多个命令。命令的执行是以 section 为单位的。
上面这些命令都会一起顺序执行,不会单独执行。

service 类型

服务是指那些需要在系统初始化时就启动或退出时自动重启的程序.
它的语法结构如下所示:

service <name> <pathname> [<argument>]*
    <option>
    <option>
    ...

service 类型 的section 表示一个可执行的程序,下面是个人工作内容中片段代码:

service submcu_srv /vendor/bin/submcu_srv
    class core
    user root
    group root
    oneshot

submcu_srv作为一个名字标识了这个 service,这个可执行程序的位置在 /vendor/bin/submcu_srv
下面的oneshot 被称为 options,options 是用来描述的 service 的特点,不同的 service 有不同的 options。

service 的执行总存在于某个 on 类型的section 中作为一个服务启动的动作(Action)。例如,这个submcu_srv可以在on fs中启动

on fs
    export OMX_BELLAGIO_REGISTRY /vendor/etc/omxregister
    start submcu_srv

例二:

service yo_service1 /system/bin/yo_service1
    class core
    user system
    disabled
    group system radio shell
    oneshot

on yo_fs
    class_start core

其中 yo_service1 这个 service 的类型是 core。
在 yo_fs 被调用的时候则将会 class_start 而执行所有类型为 core 的 service。

options(选项)

选项是用来修饰服务的。它们影响如何及何时运行这个服务.
imaged85531f1b82577c4.png

trigger(触发器)

触发器用来描述一个触发条件,当这个触发条件满足时可以执行动作。
image5d2454f71bb7b2ba.png

commands(命令)

imageba834d01164dd5ab.png

properties

init程序在运行时会更新属性系统的一些属性,提供程序内部正在执行的信息.
image620ed0de50abd4ae.png

import类型

import 类型表示包含了另外一些 section,在解析完 init.rc 后会继续调用 init_parse_config_file 来解析引入的 .rc 文件。(最新的android11不一定是这个函数了)
eg:
比如我们在 init.sc8830.rc 的开始可以看到

import /init.usb.rc
import /init.trace.rc

表示在运行完本 rc 后还将继续运行 init.usb.rc 和 init.trace.rc。

2.2 调试注意事项

在默认情况下,通过init程序启动的程序的标准输出stdout和标准错误输出stderr会重定向到/dev/null.如:

service akmd /system/bin/logwrapper /sbin/akmd

为了更方便调试你的程序,你可以使用Android的log系统,标准输出和标准错误输出会重定义到Android的log系统中来.


Android.bp简介

0 条评论 Android Android adtxl

转载自Android.bp 语法浅析-Android10.0编译系统

1.概述

在 Android 7.0 之前,Android 编译系统使用 GNU Make 描述和shell来构建编译规则,模块定义都使用Android.mk进行定义,Android.mk的本质就是Makefile,但是随着Android的工程越来越大,模块越来越多,Makefile组织的项目编译时间越来越长。这样下去Google工程师觉得不行,得要优化。
因此,在Android7.0开始,Google采用ninja来代取代之前使用的make,由于之前的Android.mk数据实在巨大,因此Google加入了一个kati工具,用于将Android.mk转换成ninja的构建规则文件buildxxx.ninja,再使用ninja来进行构建工作。
ninja的网址:https://ninja-build.org
编译速度快了一些,但是既然要干, 那就干个大的,最终目标要把make都取代,于是从Android8.0开始,Google为了进一步淘汰Makefile,因此引入了Android.bp文件来替换之前的Android.mk。
Android系统的编译历程:
image.png

2.Android.bp文件格式

根据设计,Android.bp 文件很简单。它们不包含任何条件语句,也不包含控制流语句;所有复杂问题都由用 Go 编写的构建逻辑处理。Android.bp 文件的语法和语义都尽可能与 Bazel BUILD 文件类似。

2.1 模块

Android.bp 文件中的模块以模块类型开头,后跟一组 name: "value", 格式的属性:

cc_binary {
    name: "gzip",
    srcs: ["src/test/minigzip.c"],
    shared_libs: ["libz"],
    stl: "none",
}

每个模块都必须具有 name 属性,并且相应值在所有 name 文件中必须是唯一的,仅有两个例外情况是命名空间和预构建模块中的 Android.bp 属性值,这两个值可能会重复。
srcs 属性以字符串列表的形式指定用于构建模块的源文件。您可以使用模块引用语法 ":<module-name>" 来引用生成源文件的其他模块的输出,如 genrule 或 filegroup。
如需有效模块类型及其属性的列表,请参阅 Soong 模块参考:
https://www.cnblogs.com/linhaostudy/p/12361659.html
常用模块类型:

  • cc_binary
  • cc_library
  • cc_library_static
  • android_app
  • java_library
  • hidl_interface
  • aidl_interface

2.2 类型

变量和属性是强类型,变量根据第一项赋值动态变化,属性由模块类型静态设置。支持的类型为:

  • 布尔值Bool(true 或 false)
  • 整数Integers (int)
  • 字符串Strings ("string")
  • 字符串列表List of string (["string1", "string2"])
  • 映射Maps ({key1: "value1", key2: ["value2"]})

映射可以包含任何类型的值,包括嵌套映射。列表和映射可能在最后一个值后面有终止逗号。

2.3 Glob

接受文件列表的属性(例如 srcs)也可以采用 glob 模式。glob 模式可以包含普通的 UNIX 通配符 *,例如 *.java。glob 模式还可以包含单个 ** 通配符作为路径元素,与零个或多个路径元素匹配。例如,java/**/*.java 同时匹配 java/Main.java 和 java/com/android/Main.java 模式。

2.4 变量

Android.bp 文件可能包含顶级变量赋值:

gzip_srcs = ["src/test/minigzip.c"]
cc_binary {
    srcs: gzip_srcs,
    shared_libs: ["libz"],
    stl: "none",
}

变量的作用域限定在声明它们的文件的其余部分,以及所有子 Android.bp文件,可以使用 “=” 号赋值, 但是不能使用 “:=” 赋值。变量是不可变的,但有一个例外情况:可以使用 += 赋值将变量附加到别处,但只能在引用它们之前附加。
例:

gzip_srcs = ["src/test/minigzip.c"]
gzip_srcs += [
    "src/test/test.cpp",
]
cc_binary {
    srcs: gzip_srcs,
    shared_libs: ["libz"],
    stl: "none",
}

2.5 注释

Android.bp 文件可以包含 C 样式的多行 / / 注释以及 C++ 样式的单行 // 注释。

2.6 运算符

可以使用 + 运算符附加字符串、字符串列表和映射。可以使用 + 运算符对整数求和。附加映射会生成两个映射中键的并集,并附加在两个映射中都存在的所有键的值。

2.7 条件语句

Soong 不支持 Android.bp 文件中的条件语句。但是,编译规则中需要条件语句的复杂问题将在 Go(在这种语言中,您可以使用高级语言功能,并且可以跟踪条件语句引入的隐式依赖项)中处理。大多数条件语句都会转换为映射属性,其中选择了映射中的某个值并将其附加到顶级属性。
例如,要支持特定于架构的文件,请使用以下命令:

cc_library {
    ...
    srcs: ["generic.cpp"],
    arch: {
        arm: {
            srcs: ["arm.cpp"],
        },
        x86: {
            srcs: ["x86.cpp"],
        },
    },
}

2.8 格式设置语句

Soong 包含一个针对 Blueprint 文件的规范格式设置工具,类似于 gofmt。如需以递归方式重新设置当前目录中所有 Android.bp 文件的格式,请运行以下命令:

bpfmt -w .

规范格式包括缩进四个空格、多元素列表的每个元素后面有换行符,以及列表和映射末尾有英文逗号。

2.9 默认模块

默认模块可用于在多个模块中重复使用相同的属性。例如:

cc_defaults {
    name: "gzip_defaults",
    shared_libs: ["libz"],
    stl: "none",
}

cc_binary {
    name: "gzip",
    defaults: ["gzip_defaults"],
    srcs: ["src/test/minigzip.c"],
}

2.10 预编译的模块

某些预构建的模块类型允许模块与其基于源代码的对应模块具有相同的名称。例如,如果已有同名的 cc_binary,也可以将 cc_prebuilt_binary 命名为 foo。这让开发者可以灵活地选择要纳入其最终产品中的版本。如果编译配置包含两个版本,则预编译模块定义中的 prefer 标记值会指示哪个版本具有优先级。请注意,某些预编译模块的名称不能以 prebuilt开头,例如 android_app_import。

2.11 命名空间模块

在 Android 完全从 Make 转换为 Soong 之前,Make 产品配置必须指定 PRODUCT_SOONG_NAMESPACES 值。它的值应该是一个以空格分隔的列表,其中包含 Soong 导出到 Make 以使用 m 命令进行编译的命名空间。在 Android 完成到 Soong 的转换之后,启用命名空间的详细信息可能会发生变化。
Soong 可以让不同目录中的模块指定相同的名称,只要每个模块都在单独的命名空间中声明即可。可以按如下方式声明命名空间:

soong_namespace {
    imports: ["path/to/otherNamespace1", "path/to/otherNamespace2"],
}

请注意,命名空间没有 name 属性;其路径会自动指定为其名称。
系统会根据每个 Soong 模块在树中的位置为其分配命名空间。每个 Soong 模块都会被视为处于 Android.bp(位于当前目录或最近的父级目录中的 soong_namespace 文件内)定义的命名空间中。如果未找到此类 soong_namespace 模块,则认为该模块位于隐式根命名空间中。
下面是一个示例:Soong尝试解析由模块 M 在名称空间 N(导入命名空间 I1、I2、I3…)中声明的依赖项D。

  1. 如果 D 是 //namespace:module 格式的完全限定名称,系统将仅在指定的命名空间中搜索指定的模块名称。
  2. 否则,Soong 将首先查找在命名空间 N 中声明的名为 D 的模块。
  3. 如果该模块不存在,Soong 会在命名空间 I1、I2、I3…中查找名为 D 的模块。
  4. 最后,Soong 在根命名空间中查找。

2.12 Android.mk文件 自动转Android.bp方法

Android源码里边提供了快捷直接Android.mk转换成Android.bp的工具:androidmk。
androidmk源码位置:

 build/soong/androidmk/cmd/androidmk/androidmk.go

编译出来后androidmk可执行文件位置:

aosp/out/soong/host/linux-x86/bin/androidmk

转换方法:

aosp/out/soong/host/linux-x86/bin/androidmk [Android.mk PATH] > [Android.bp PATH]

该工具可以转换变量、模块、注释和一些条件,但任何自定义生成文件规则、复杂条件或额外的包含都必须手动转换。


Android.mk简介

0 条评论 Android 无标签 adtxl

Android.mk

本页介绍了 ndk-build 所使用的 Android.mk 构建文件的语法。

概览

Android.mk 文件位于项目 jni/ 目录的子目录中,用于向构建系统描述源文件和共享库。它实际上是一个微小的 GNU makefile 片段,构建系统会将其解析一次或多次。Android.mk 文件用于定义 Application.mk、构建系统和环境变量所未定义的项目级设置。它还可替换特定模块的项目级设置。

Android.mk 的语法支持将源文件分组为“模块”。模块是静态库、共享库或独立的可执行文件。您可在每个 Android.mk 文件中定义一个或多个模块,也可在多个模块中使用同一个源文件。构建系统只将共享库放入您的应用软件包。此外,静态库可生成共享库。

除了封装库之外,构建系统还可为您处理各种其他事项。例如,您无需在 Android.mk 文件中列出头文件或生成的文件之间的显式依赖关系。NDK 构建系统会自动计算这些关系。因此,您应该能够享受到未来 NDK 版本中支持的新工具链/平台功能带来的益处,而无需处理 Android.mk 文件。

此文件的语法与随整个 Android 开源项目分发的 Android.mk 文件中使用的语法非常接近。虽然使用这些语法的构建系统实现并不相同,但通过有意将语法设计得相似,可使应用开发者更轻松地将源代码重复用于外部库。

基础知识

在详细了解语法之前,最好先了解 Android.mk 文件所含内容的基本信息。为此,本部分使用 Hello-JNI 示例中的 Android.mk 文件解释文件中每一行的作用。

Android.mk 文件必须先定义 LOCAL_PATH 变量:

LOCAL_PATH := $(call my-dir)

此变量表示源文件在开发树中的位置。在上述命令中,构建系统提供的宏函数 my-dir 将返回当前目录(Android.mk 文件本身所在的目录)的路径。

下一行声明 CLEAR_VARS 变量,其值由构建系统提供。

include $(CLEAR_VARS)

CLEAR_VARS 变量指向一个特殊的 GNU Makefile,后者会为您清除许多 LOCAL_XXX 变量,例如 LOCAL_MODULE、LOCAL_SRC_FILES 和 LOCAL_STATIC_LIBRARIES。请注意,GNU Makefile 不会清除 LOCAL_PATH。此变量必须保留其值,因为系统在单一 GNU Make 执行上下文(其中的所有变量都是全局变量)中解析所有构建控制文件。在描述每个模块之前,您必须声明(重新声明)此变量。

接下来,LOCAL_MODULE 变量存储您要构建的模块的名称。请在应用的每个模块中使用一次此变量。

LOCAL_MODULE := hello-jni

每个模块名称必须唯一,且不含任何空格。构建系统在生成最终共享库文件时,会对您分配给 LOCAL_MODULE 的名称自动添加正确的前缀和后缀。例如,上述示例会生成名为 libhello-jni.so 的库。

注意:如果模块名称的开头已经是 lib,构建系统不会添加额外的 lib 前缀;而是按原样采用模块名称,并添加 .so 扩展名。因此,比如原来名为 libfoo.c 的源文件仍会生成名为 libfoo.so 的共享对象文件。此行为是为了支持 Android 平台源文件根据 Android.mk 文件生成的库;所有这些库的名称都以 lib 开头。

下一行会列举源文件,以空格分隔多个文件:

LOCAL_SRC_FILES := hello-jni.c

LOCAL_SRC_FILES 变量必须包含要构建到模块中的 C 和/或 C++ 源文件列表。

最后一行帮助系统将一切连接到一起:

include $(BUILD_SHARED_LIBRARY)

BUILD_SHARED_LIBRARY 变量指向一个 GNU Makefile 脚本,该脚本会收集您自最近 include 以来在 LOCAL_XXX 变量中定义的所有信息。此脚本确定要构建的内容以及构建方式。

在示例目录中有更为复杂的示例,包括带有注释的 Android.mk 文件供您查看。此外,示例:native-activity 详细介绍了该示例的 Android.mk 文件。最后,变量和宏提供了关于本部分中变量的更多信息。

变量和宏

构建系统提供许多可在 Android.mk 文件中使用的变量。其中的许多变量已预先赋值。另一些变量由您赋值。

除了这些变量之外,您还可以自己定义任意变量。在定义变量时请注意,NDK 构建系统保留了下列变量名称:

  • 以 LOCAL_ 开头的名称,例如 LOCAL_MODULE。
  • 以 PRIVATE_、NDK_ 或 APP 开头的名称。构建系统在内部使用这些变量名。
  • 小写名称,例如 my-dir。构建系统也是在内部使用这些变量名。

如果您需要在 Android.mk 文件中定义您自己的便利变量,建议在名称前附加 MY_。

NDK定义的include变量

本部分探讨了构建系统在解析 Android.mk 文件前定义的 GNU Make 变量。在某些情况下,NDK 可能会多次解析 Android.mk 文件,每次使用其中某些变量的不同定义。

CLEAR_VARS

此变量指向的构建脚本用于取消定义下文“开发者定义的变量”部分中列出的几乎所有 LOCAL_XXX 变量。在描述新模块之前,请使用此变量来包含此脚本。使用它的语法为:

include $(CLEAR_VARS)

BUILD_EXECUTABLE

此变量指向的构建脚本会收集您在 LOCAL_XXX 变量中提供的模块的所有相关信息,以及确定如何根据您列出的源文件构建目标可执行文件。请注意,使用此脚本要求您至少已经为 LOCAL_MODULE 和 LOCAL_SRC_FILES 赋值;如需详细了解这些变量,请参阅模块描述变量。
使用此变量的语法为:

include $(BUILD_EXECUTABLE)

注意:大多数 Android 应用不包含可执行文件,但它们对于创建单元测试和其他调试工具很有用。

BUILD_SHARED_LIBRARY

此变量指向的构建脚本会收集您在 LOCAL_XXX 变量中提供的模块的所有相关信息,以及确定如何根据您列出的源文件构建目标共享库。请注意,使用此脚本要求您至少已经为 LOCAL_MODULE 和 LOCAL_SRC_FILES 赋值;如需详细了解这些变量,请参阅模块描述变量。

使用此变量的语法为:

include $(BUILD_SHARED_LIBRARY)

共享库变量会导致构建系统生成扩展名为 .so 的库文件。

BUILD_STATIC_LIBRARY

用于构建静态库的 BUILD_SHARED_LIBRARY 的变体。构建系统不会将静态库复制到您的项目/软件包中,但可以使用静态库构建共享库(请参阅下文的 LOCAL_STATIC_LIBRARIES 和 LOCAL_WHOLE_STATIC_LIBRARIES)。使用此变量的语法为:

include $(BUILD_STATIC_LIBRARY)

静态库变量会导致构建系统生成扩展名为 .a 的库。

PREBUILT_SHARED_LIBRARY

指向用于指定预构建共享库的构建脚本。与 BUILD_SHARED_LIBRARY 和 BUILD_STATIC_LIBRARY 的情况不同,这里的 LOCAL_SRC_FILES 值不能是源文件,而必须是指向预构建共享库的单一路径,例如 foo/libfoo.so。使用此变量的语法为:

include $(PREBUILT_SHARED_LIBRARY)

您也可以使用 LOCAL_PREBUILTS 变量引用另一个模块中的预构建库。如需详细了解如何使用预构建库,请参阅使用预构建库

PREBUILT_STATIC_LIBRARY

与 PREBUILT_SHARED_LIBRARY 相同,但用于预构建静态库。如需详细了解如何使用预构建库,请参阅使用预构建库

目标信息变量

构建系统会根据 APP_ABI 变量所指定的每个 ABI 分别解析 Android.mk 一次,该变量通常在 Application.mk 文件中定义。如果 APP_ABI 为 all,构建系统会根据 NDK 支持的每个 ABI 分别解析 Android.mk 一次。本部分介绍构建系统每次解析 Android.mk 时定义的变量。

TARGET_ARCH

构建系统解析此 Android.mk 文件时指向的 CPU 系列。此变量将是下列其中一项:arm、arm64、x86 或 x86_64。

TARGET_PLATFORM

构建系统解析此 Android.mk 文件时指向的 Android API 级别号。例如,Android 5.1 系统映像对应于 Android API 级别 22:android-22。如需查看平台名称和相应 Android 系统映像的完整列表,请参阅原生 API。以下示例展示了使用此变量的语法:

ifeq ($(TARGET_PLATFORM),android-22)
    # ... do something ...
endif

TARGET_ARCH_ABI

构建系统解析此 Android.mk 文件时指向的 ABI。表 1 显示了用于每个受支持 CPU 和架构的 ABI 设置。

表 1. 不同 CPU 和架构的 ABI 设置。

CPU和架构 设置
ARMv7 armeabi-v7a
ARMv8 AArch64 arm64-v8a
i686 x86
x86-64 x86_64

以下示例演示了如何检查 ARMv8 AArch64 是否为目标 CPU 与 ABI 的组合:

ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
  # ... do something ...
endif

如需详细了解架构 ABI 和相关兼容性问题,请参阅 Android ABI
未来的新目标 ABI 将使用不同的值。

TARGET_ABI

目标 Android API 级别与 ABI 的串联,特别适用于要针对实际设备测试特定目标系统映像的情况。例如,要检查在 Android API 级别 22 上运行的 64 位 ARM 设备:

ifeq ($(TARGET_ABI),android-22-arm64-v8a)
  # ... do something ...
endif

模块描述变量

本部分中的变量会向构建系统描述您的模块。每个模块描述都应遵守以下基本流程:

  1. 使用 CLEAR_VARS 变量初始化或取消定义与模块相关的变量。
  2. 为用于描述模块的变量赋值。
  3. 使用 BUILD_XXX 变量设置 NDK 构建系统,使其将适当的构建脚本用于该模块。

LOCAL_PATH

此变量用于指定当前文件的路径。必须在 Android.mk 文件开头定义此变量。以下示例演示了如何定义此变量:

LOCAL_PATH := $(call my-dir)

CLEAR_VARS 所指向的脚本不会清除此变量。因此,即使 Android.mk 文件描述了多个模块,您也只需定义此变量一次。

LOCAL_MODULE

此变量用于存储模块名称。指定的名称在所有模块名称中必须唯一,并且不得包含任何空格。您必须先定义该名称,然后才能添加任何脚本(CLEAR_VARS 的脚本除外)。无需添加 lib 前缀或 .so 或 .a 文件扩展名;构建系统会自动执行这些修改。在整个 Android.mk 和 Application.mk 文件中,请用未经修改的名称引用模块。例如,以下行会导致生成名为 libfoo.so 的共享库模块:

LOCAL_MODULE := "foo"

如果您希望生成的模块使用除“lib + LOCAL_MODULE 的值”以外的名称,可以使用 LOCAL_MODULE_FILENAME 变量为生成的模块指定自己选择的名称。

LOCAL_MODULE_FILENAME

此可选变量使您能够替换构建系统为其生成的文件默认使用的名称。例如,如果 LOCAL_MODULE 的名称为 foo,您可以强制系统将其生成的文件命名为 libnewfoo。以下示例演示了如何完成此操作:

LOCAL_MODULE := foo
LOCAL_MODULE_FILENAME := libnewfoo

对于共享库模块,此示例将生成一个名为 libnewfoo.so 的文件。

注意:您无法替换文件路径或文件扩展名。

LOCAL_SRC_FILES

此变量包含构建系统生成模块时所用的源文件列表。只列出构建系统实际传递到编译器的文件,因为构建系统会自动计算所有相关的依赖项。请注意,您可以使用相对(相对于 LOCAL_PATH)和绝对文件路径。

建议您避免使用绝对文件路径;相对路径可以提高 Android.mk 文件的移植性。

注意:务必在构建文件中使用 Unix 样式的正斜杠 (/)。构建系统无法正确处理 Windows 样式的反斜杠 ()。

LOCAL_CPP_EXTENSION

可以使用此可选变量为 C++ 源文件指定 .cpp 以外的文件扩展名。例如,以下行将扩展名更改为 .cxx(设置必须包含点)。

LOCAL_CPP_EXTENSION := .cxx

您可以使用此变量指定多个扩展名。例如:

LOCAL_CPP_EXTENSION := .cxx .cpp .cc

LOCAL_CPP_FEATURES

您可使用此可选变量指明您的代码依赖于特定 C++ 功能。它会在构建过程中启用正确的编译器标记和链接器标记。对于预构建的二进制文件,此变量还会声明二进制文件依赖于哪些功能,从而确保最终链接正常运行。我们建议您使用此变量,而不要直接在 LOCAL_CPPFLAGS 定义中启用 -frtti 和 -fexceptions。

使用此变量可让构建系统对每个模块使用适当的标记。使用 LOCAL_CPPFLAGS 会导致编译器将所有指定的标记用于所有模块,而不管实际需求如何。

例如,如需指明您的代码使用 RTTI(运行时类型信息),请写入:

LOCAL_CPP_FEATURES := rtti

如需指明您的代码使用 C++ 异常,请输入:

LOCAL_CPP_FEATURES := exceptions

您还可以为此变量指定多个值。例如:

LOCAL_CPP_FEATURES := rtti features

描述值的顺序无关紧要。

LOCAL_C_INCLUDES

您可使用此可选变量指定相对于 NDK root 目录的路径列表,以便在编译所有源文件(C、C++ 和 Assembly)时添加到 include 搜索路径中。例如:

LOCAL_C_INCLUDES := sources/foo

或者甚至:

LOCAL_C_INCLUDES := $(LOCAL_PATH)/<subdirectory>/foo

请在通过 LOCAL_CFLAGS 或 LOCAL_CPPFLAGS 设置任何对应的包含标记前定义此变量。

在使用 ndk-gdb 启动原生调试时,构建系统也会自动使用 LOCAL_C_INCLUDES 路径。

LOCAL_CFLAGS

此可选变量用于设置在构建 C 和 C++ 源文件时构建系统要传递的编译器标记。这样,您就可以指定额外的宏定义或编译选项。可以使用 LOCAL_CPPFLAGS 仅为 C++ 指定标记。

请勿尝试在 Android.mk 文件中更改优化/调试级别。构建系统可以使用 Application.mk 文件中的相关信息自动处理此设置。这样,构建系统就可以生成供调试期间使用的有用数据文件。

您可通过输入以下代码指定额外的 include 路径:

LOCAL_CFLAGS += -I<path>,

但是,最好使用 LOCAL_C_INCLUDES,因为这样也可以使用可用于 ndk-gdb 原生调试的路径。

LOCAL_CPPFLAGS

只构建 C++ 源文件时将传递的一组可选编译器标记。它们将出现在编译器命令行中的 LOCAL_CFLAGS 后面。使用 LOCAL_CFLAGS 为 C 和 C++ 指定标记。

LOCAL_STATIC_LIBRARIES

此变量用于存储当前模块依赖的静态库模块列表。

如果当前模块是共享库或可执行文件,此变量将强制这些库链接到生成的二进制文件。

如果当前模块是静态库,此变量只是指出依赖于当前模块的其他模块也会依赖于列出的库。

LOCAL_SHARED_LIBRARIES

此变量会列出此模块在运行时依赖的共享库模块。此信息是链接时必需的信息,用于将相应的信息嵌入到生成的文件中。

LOCAL_WHOLE_STATIC_LIBRARIES

此变量是 LOCAL_STATIC_LIBRARIES 的变体,表示链接器应将相关的库模块视为完整归档。如需详细了解完整归档,请参阅有关 --whole-archive 标记的 GNU Id 文档

多个静态库之间存在循环依赖关系时,此变量十分有用。使用此变量构建共享库时,它将强制构建系统将静态库中的所有对象文件添加到最终二进制文件。但是,生成可执行文件时不会发生这种情况。

LOCAL_LDLIBS

此变量列出了在构建共享库或可执行文件时使用的额外链接器标记。利用此变量,您可使用 -l 前缀传递特定系统库的名称。例如,以下示例指示链接器生成在加载时链接到 /system/lib/libz.so 的模块:

LOCAL_LDLIBS := -lz

如需查看此 NDK 版本中可以链接的公开系统库列表,请参阅原生 API

注意:如果您为静态库定义此变量,构建系统会忽略此变量,并且 ndk-build 显示一则警告。

LOCAL_LDFLAGS

此变量列出了构建系统在构建共享库或可执行文件时使用的其他链接器标记。例如,若要在 ARM/X86 上使用 ld.bfd 链接器:

LOCAL_LDFLAGS += -fuse-ld=bfd

注意:如果您为静态库定义此变量,构建系统会忽略此变量,并且 ndk-build 会显示一则警告。

LOCAL_ALLOW_UNDEFINED_SYMBOLS

默认情况下,如果构建系统在尝试构建共享库时遇到未定义的引用,将会抛出“未定义的符号”错误。此错误可帮助您捕获源代码中的错误。

如需停用此检查,请将此变量设置为 true。请注意,此设置可能会导致共享库在运行时加载。

注意:如果您为静态库定义此变量,构建系统会忽略此变量,并且 ndk-build 会显示一则警告。

LOCAL_ARM_MODE

默认情况下,构建系统会以 thumb 模式生成 ARM 目标二进制文件,其中每条指令都是 16 位宽,并与 thumb/ 目录中的 STL 库链接。将此变量定义为 arm 会强制构建系统以 32 位 arm 模式生成模块的对象文件。以下示例演示了如何执行此操作:

LOCAL_ARM_MODE := arm

您也可以对源文件名附加 .arm 后缀,指示构建系统仅以 arm 模式构建特定的源文件。例如,以下示例指示构建系统始终以 ARM 模式编译 bar.c,但根据 LOCAL_ARM_MODE 的值构建 foo.c。

LOCAL_SRC_FILES := foo.c bar.c.arm

注意:您也可以在 Application.mk 文件中将 APP_OPTIM 设置为 debug,强制构建系统生成 ARM 二进制文件。指定 debug 会强制构建 ARM,因为工具链调试程序无法正确处理 Thumb 代码。

LOCAL_ARM_NEON

此变量仅在以 armeabi-v7a ABI 为目标时才有意义。它允许在 C 和 C++ 源文件中使用 ARM Advanced SIMD (NEON) 编译器内建函数,以及在 Assembly 文件中使用 NEON 指令。

请注意,并非所有基于 ARMv7 的 CPU 都支持 NEON 扩展指令集。因此,必须执行运行时检测,以便在运行时安全地使用此代码。如需了解详情,请参阅 Neon 支持和 CPU 功能。

此外,您也可以使用 .neon 后缀,指定构建系统仅以 NEON 支持来编译特定源文件。在以下示例中,构建系统以 Thumb 和 NEON 支持编译 foo.c,以 Thumb 支持编译 bar.c,并以 ARM 和 NEON 支持编译 zoo.c:

LOCAL_SRC_FILES = foo.c.neon bar.c zoo.c.arm.neon

如果同时使用这两个后缀,.arm 必须在 .neon 前面。

LOCAL_DISABLE_FORMAT_STRING_CHECKS

默认情况下,构建系统会在编译代码时保护格式字符串。这样的话,如果 printf 样式的函数中使用了非常量格式的字符串,就会强制引发编译器错误。此保护默认启用,但您也可通过将此变量的值设置为 true 将其停用。如果没有必要的原因,我们不建议停用。

LOCAL_EXPORT_CFLAGS

此变量用于记录一组 C/C++ 编译器标记,这些标记将添加到通过 LOCAL_STATIC_LIBRARIES 或 LOCAL_SHARED_LIBRARIES 变量使用此模块的任何其他模块的 LOCAL_CFLAGS 定义中。

例如,假设有以下模块对:foo 和 bar,它们依赖于 foo:

include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo/foo.c
LOCAL_EXPORT_CFLAGS := -DFOO=1
include $(BUILD_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.c
LOCAL_CFLAGS := -DBAR=2
LOCAL_STATIC_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)

在这里,构建系统在构建 bar.c 时会向编译器传递 -DFOO=1 和 -DBAR=2 标记。它还会在模块的 LOCAL_CFLAGS 前面加上导出的标记,以便您轻松进行替换。

此外,模块之间的关系也具有传递性:如果 zoo 依赖于 bar,而后者依赖于 foo,那么 zoo 也会继承从 foo 导出的所有标记。

最后,构建系统在执行局部构建时(即,构建要导出标记的模块时),不使用导出的标记。因此,在以上示例中,构建系统在构建 foo/foo.c 时不会将 -DFOO=1 传递到编译器。如需执行局部构建,请改用 LOCAL_CFLAGS。

LOCAL_EXPORT_CPPFLAGS

此变量与 LOCAL_EXPORT_CFLAGS 相同,但仅适用于 C++ 标记。

LOCAL_EXPORT_C_INCLUDES

此变量与 LOCAL_EXPORT_CFLAGS 相同,但适用于 C include 路径。例如,当 bar.c 需要包括模块 foo 的头文件时,此变量很有用。

LOCAL_EXPORT_LDFLAGS

此变量与 LOCAL_EXPORT_CFLAGS 相同,但适用于链接器标记。

LOCAL_EXPORT_LDLIBS

此变量与 LOCAL_EXPORT_CFLAGS 相同,用于指示构建系统将特定系统库的名称传递到编译器。请在您指定的每个库名称前附加 -l。

请注意,构建系统会将导入的链接器标记附加到模块的 LOCAL_LDLIBS 变量值上。其原因在于 Unix 链接器的工作方式。

当模块 foo 是静态库并且具有依赖于系统库的代码时,此变量通常很有用。然后,您可以使用 LOCAL_EXPORT_LDLIBS 导出依赖项。例如:

include $(CLEAR_VARS)
LOCAL_MODULE := foo
LOCAL_SRC_FILES := foo/foo.c
LOCAL_EXPORT_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := bar
LOCAL_SRC_FILES := bar.c
LOCAL_STATIC_LIBRARIES := foo
include $(BUILD_SHARED_LIBRARY)

在此示例中,构建系统在构建 libbar.so 时,将在链接器命令的末尾指定 -llog。这样就会告知链接器,由于 libbar.so 依赖于 foo,因此它也依赖于系统日志记录库。

LOCAL_SHORT_COMMANDS

当您的模块有很多源文件和/或依赖的静态或共享库时,请将此变量设置为 true。这样会强制构建系统将 @ 语法用于包含中间对象文件或链接库的归档。

此功能在 Windows 上可能很有用,在 Windows 上,命令行最多只接受 8191 个字符,这对于复杂的项目来说可能太少。它还会影响个别源文件的编译,而且将几乎所有编译器标记都放在列表文件内。

请注意,true 以外的任何值都将恢复为默认行为。您也可以在 Application.mk 文件中定义 APP_SHORT_COMMANDS,对项目中的所有模块强制实施此行为。

我们不建议默认启用此功能,因为它会减慢构建速度。

LOCAL_THIN_ARCHIVE

构建静态库时,请将此变量设置为 true。这样会生成一个瘦归档,即一个库文件,其中不含对象文件,而只包含它通常包含的实际对象的文件路径。

这对于减小构建输出的大小非常有用。但缺点是,这样的库无法移至其他位置(其中的所有路径都是相对路径)。

有效值为 true、false 或空白。您可在 Application.mk 文件中通过 APP_THIN_ARCHIVE 变量来设置默认值。

注意:在非静态库模块或预构建的静态库模块中,将会忽略此变量。

LOCAL_FILTER_ASM

请将此变量定义为一个 shell 命令,供构建系统用于过滤根据您为 LOCAL_SRC_FILES 指定的文件提取或生成的汇编文件。定义此变量会导致发生以下情况:

构建系统从任何 C 或 C++ 源文件生成临时汇编文件,而不是将它们编译到对象文件中。
构建系统在任何临时汇编文件以及 LOCAL_SRC_FILES 中所列任何汇编文件的 LOCAL_FILTER_ASM 中执行 shell 命令,因此会生成另一个临时汇编文件。
构建系统将这些过滤的汇编文件编译到对象文件中。
例如:

LOCAL_SRC_FILES  := foo.c bar.S
LOCAL_FILTER_ASM :=

foo.c --1--> $OBJS_DIR/foo.S.original --2--> $OBJS_DIR/foo.S --3--> $OBJS_DIR/foo.o
bar.S                                 --2--> $OBJS_DIR/bar.S --3--> $OBJS_DIR/bar.o

“1”对应于编译器,“2”对应于过滤器,“3”对应于汇编程序。过滤器必须是一个独立的 shell 命令,它接受输入文件名作为第一个参数,接受输出文件名作为第二个参数。例如:

myasmfilter $OBJS_DIR/foo.S.original $OBJS_DIR/foo.S
myasmfilter bar.S $OBJS_DIR/bar.S

NDK 提供的函数宏

本部分介绍了 NDK 提供的 GNU Make 函数宏。使用$(call <function>)可以对其进行求值;其返回文本信息。

my-dir

这个宏返回最后包括的 makefile 的路径,通常是当前 Android.mk 的目录。my-dir 可用于在 Android.mk 文件开头定义 LOCAL_PATH。例如:

LOCAL_PATH := $(call my-dir)

由于 GNU Make 的工作方式,这个宏实际返回的是构建系统解析构建脚本时包含的最后一个 makefile 的路径。因此,包括其他文件后就不应调用 my-dir。

例如:

LOCAL_PATH := $(call my-dir)

# ... declare one module

include $(LOCAL_PATH)/foo/`Android.mk`

LOCAL_PATH := $(call my-dir)

# ... declare another module

这里的问题在于,对 my-dir 的第二次调用将 LOCAL_PATH 定义为 $PATH/foo,而不是 $PATH,因为这是其最近的 include 所指向的位置。

在 Android.mk 文件中的任何其他内容后指定额外的 include 可避免此问题。例如:

LOCAL_PATH := $(call my-dir)

# ... declare one module

LOCAL_PATH := $(call my-dir)

# ... declare another module

# extra includes at the end of the Android.mk file
include $(LOCAL_PATH)/foo/Android.mk

如果以这种方式构造文件不可行,请将第一个 my-dir 调用的值保存到另一个变量中。例如:

MY_LOCAL_PATH := $(call my-dir)

LOCAL_PATH := $(MY_LOCAL_PATH)

# ... declare one module

include $(LOCAL_PATH)/foo/`Android.mk`

LOCAL_PATH := $(MY_LOCAL_PATH)

# ... declare another module

all-subdir-makefiles

返回位于当前 my-dir 路径所有子目录中的 Android.mk 文件列表。

利用此函数,您可以为构建系统提供深度嵌套的源目录层次结构。默认情况下,NDK 只在 Android.mk 文件所在的目录中查找文件。

this-makefile

返回当前 makefile(构建系统从中调用函数)的路径。

parent-makefile

返回包含树中父 makefile 的路径(包含当前 makefile 的 makefile 的路径)。

grand-parent-makefile

返回包含树中祖父 makefile 的路径(包含当前父 makefile 的 makefile 的路径)。

import-module

此函数用于按模块名称来查找和包含模块的 Android.mk 文件。典型的示例如下所示:

$(call import-module,<name>)

在此示例中,构建系统在 NDK_MODULE_PATH 环境变量所引用的目录列表中查找具有 标记的模块,并且自动包括其 Android.mk 文件。

google Android.mk