Android构建系统:Android.mk(2)函数详解

简介: Android构建系统:Android.mk(2)函数详解

在这篇博客中,我将介绍Android.mk详解中的其中一个部分【函数】和make/m等命令概念的区分,Android.mk文件中使用Makefile中函数的概念和用法,以及一些常用的内置函数和自定义函数。通过使用函数,可以简化和优化Makefile中的字符串操作,可以实现一些复杂的功能。我会附上详细的测试代码和测试结果。。

Android构建系统:Android.mk(1)基础概念赋值变量引用详解

Android构建系统:Android.mk(2)函数详解

Android构建系统:Android.mk(3)条件控制详解

我看到Android.mk里面的函数,最开始概念模糊的时候,闪过一个疑问:

Android.mk 为什么能使用Makefile函数?

查阅资料了解到:

需要先搞清楚Android.mkMakefile的差异,Android.mk是早期Android构建系统的一部分,能够执行Android.mk的工具叫makemake是用来搞自动化编译和构建的。那make的可执行对象有哪些呢?第一印象当然是Makefile了,但是查阅资料发现Android.mk 其实是Android构建系统定制的Makefile

可以理解为原厂和ODM(定制)的区别吧?所以可以使用所有 make 提供的标准函数和变量,以及 Android 构建系统提供的一些特定扩展。Android.mk 的构建系统是基于 make 工具的。

测试代码和结果

完整的测试结果:

完整的测试代码:

$(warning ====== 测试开始 ======)
#LOCAL_PATH := $(call my-dir)
# 使用subst 函数
# subst 函数用于替换字符串中的某些字符
# 它接受三个参数,分别是要替换的字符、替换后的字符和原始字符串
# 它会返回替换后的字符串
$(info [SUBST] $(subst a,b,cat) <- (cat: a -> b)) # 将cat 中的a 替换为b,结果为cbt
$(info [SUBST] $(subst .c,.o,foo.c) <- (foo.c: .c -> .o)) # 将foo.c 中的.c 替换为.o,结果为foo.o
# 使用patsubst 函数
# patsubst 函数用于根据模式替换字符串中的某些字符
# 它接受三个参数,分别是要替换的模式、替换后的模式和原始字符串
# 它会返回替换后的字符串
# 模式中可以使用% 表示任意长度的字符
# 将.c 替换为.o,结果为foo.o bar.o baz.o
$(info [PATSUBST] $(patsubst %.c,%.o,foo.c bar.c baz.c) <- (%.c -> %.o)) 
# 将foo 开头的字符串替换为bar 开头的字符串,结果为barbar barbaz barqux
$(info [PATSUBST] $(patsubst foo%,bar%,foobar foobaz fooqux) <- (foo% -> bar%)) 
# 使用strip 函数
# strip 函数用于去除字符串中的多余空格
# 它接受一个参数,即原始字符串
# 它会返回去除空格后的字符串
$(info [STRIP] "$(strip   hello   example02  )" <- 去除多余空格) # 去除多余空格,结果为hello example02
# 使用wildcard 函数
# wildcard 函数用于根据模式匹配文件名
# 它接受一个参数,即匹配模式
# 它会返回匹配到的文件名列表
# 模式中可以使用* 表示任意长度的字符,? 表示任意单个字符,[...] 表示任意一个在括号内的字符
# 匹配所有以.c 结尾的文件名,结果为foo.c(假设当前目录下只有这个文件)
$(info [WILDCARD] $(wildcard *.c) <- 匹配所有以.c 结尾的文件名) 
# 匹配所有以foo 开头,一个任意字符,.c 结尾的文件名,结果为空(假设当前目录下没有这样的文件)
$(info [WILDCARD] $(wildcard foo?.c) <- 匹配所有以foo 开头,一个任意字符,.c 结尾的文件名) 
# 匹配所有以foo 开头,a 或b 或c,.c 结尾的文件名,结果为空(假设当前目录下没有这样的文件)
$(info [WILDCARD] $(wildcard foo[abc].c) <- 匹配所有以foo 开头,a 或b 或c,.c 结尾的文件名) 
# 使用foreach 函数
# foreach 函数用于对列表中的每个元素执行某个操作
# 它接受三个参数,分别是变量名、列表和操作表达式
# 它会返回操作后的结果列表
# 对每个元素乘以2,并打印结果
$(info [FOREACH] $(foreach x,2 0 2 3 ,$(x) * 2 = $(shell expr $(x) \* 2))) 
# 使用if 函数
# 如果Ln28 已经定义,打印Ln28 is defined,否则打印Ln28 is not defined
$(info $(if $(Ln28),Ln28 is defined,Ln28 is not defined)) 
# 如果FILE 是一个以.c 结尾的文件名,打印FILE is a C source file,否则打印FILE is not a C source file
FILE := fooa.c
$(info $(if $(filter %.c,$(FILE)),$(FILE) is a C source file,$(FILE) is not a C source file)) 
# 定义一个自定义函数,用于计算两个数的和
define add
$(shell expr $(1) + $(2))
endef
# 使用call 函数调用自定义函数
$(info call add:$(call add,2,0)) # 调用add 函数,传递2 和0 作为参数,结果为2
$(info call add:$(call add,2,3)) # 调用add 函数,传递2 和3 作为参数,结果为5
# 定义一个自定义函数,用于给字符串加前缀
define addprefix 
$($(1) + $(2))
endef
# 调用自定义addprefix 函数,给world 和example02前面加上hello ,结果为hello world hello example02
$(info $(addprefix hello ,world example02)) 
$(warning ====== 测试结束 ======)
all:
  @echo "测试example02结束!"

make和m,mm,mmm概念区分

首先我们看到Android.mk 是不是立马想到了(如 mmmmmm 等)等命令来执行?

但不然,我们可以使用 make -f Android.mk 命令的目的是告诉 make 工具使用指定的 Android.mk 文件作为其构建规则文件,而不是默认的 Makefile 文件,为什么要使用make -f ,而不是make?

因为make 是一个构建工具,它使用特定格式的文件(基本上名字是 Makefile)来确定如何构建项目。这个文件包含了构建规则、目标和依赖关系。

-f 选项允许指定一个不同的文件作为 make 的输入。加f-f的意思是多个构建文件或不使用默认名称 Makefile 的情况下。

ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example02$ make 
make: *** 没有指明目标并且找不到 makefile。 停止。
#### failed to build some targets  ####
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example02$ mv Android.mk makefile
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example02$ make 
makefile:1: ====== 测试开始 ======
。。。。省略了,就是完整的测试结果的打印
makefile:71: ====== 测试结束 ======
测试example02结束!
#### build completed successfully  ####

make -f Android.mk 命令的作用是使用 Android.mk 文件作为构建规则来运行 make,但在实际的 Android 构建过程中,这不是常用的命令只是为了验证Makefile的一些内容。

那么什么时候使用 make 什么时候时候(如 mmmmmm 等)等命令来执行?

其实可以这样简单快速区分(但不完全是这么理解啊),

刚开始理解,死记构建一个目标模块用( mmmmmm ),这个目标比如是指include $(BUILD_EXECUTABLE),在我example01的测试代码中,这个就是为了构建一个exec01可执行文件,可执行文件就是一个目标!

小节一下:

  1. make:
  • make 是一个通用的构建工具,用于处理 makefile 文件中定义的构建规则。
  • 在 Android 构建系统中,源码根目录直接运行 make 会根据顶层 Makefile(和其他相关的 *.mk 文件)构建整个 Android 系统,如果在 example02 目录下运行 make 则会找 example02 目录下的 makefile ,如果 make -f Android.mk ,则是指定一个运行的名字。
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk$ make
  1. m:
  • 这是一个快捷命令,等同于在 源代码的根目录下运行 make
  • 它用于构建整个 Android 系统。
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk$ m #m == make
  1. mm:
  • 这个命令专门用于构建当前目录下的模块。
  • 它是为了方便在特定目录下快速构建和测试模块。
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example02$ mm
  1. mmm:
  • 这个命令用于构建指定目录下的所有模块。
  • 它允许指定一个目录并构建该目录下的所有模块。
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk$ mmm vendor/customize/demo/example02/

上面反复加强记忆,那么现在可以这样理解:

  • make 是一个通用的构建工具,它根据 Makefile 或其他指定的文件中的规则来执行构建。在 Android 构建系统中,它通常用于构建整个系统。
  • m, mm, 和 mmm 是 Android 构建系统提供的快捷命令,更有针对性地构建模块。这些命令背后仍然是 make 工具,但这些命令的作用是提供了一个更简洁的方式来指定构建目标的方式。

Makefile中的函数

在Makefile中有时(特别是大型项目,文件特别多的那种,需要快速适配写出一个能自动化构建的文件)需要对一些字符串进行操作,例如替换、匹配、去除空格等。为了方便这些操作,Makefile提供了一些内置的函数,它们可以接受参数并返回结果。除了内置函数,还可以自己定义一些函数,以实现一些特定的功能。

这就是本文重点,将介绍Makefile中函数的使用方法和一些常用的函数。

函数的一般形式

函数是一种特殊的变量,它可以接受参数并返回结果。函数的一般形式为:

$(函数名 参数1,参数2,...)

函数名是一个已经定义好的函数,参数是一个或多个以逗号分隔的表达式,函数会根据参数计算并返回结果。例如:

# 定义一个自定义函数,用于给字符串加前缀
define addprefix 
$($(1) + $(2))
endef
# 调用自定义addprefix 函数,给world 和example02前面加上hello ,结果为hello world hello example02
$(info $(addprefix hello ,world example02))

内置函数

Makefile中有很多内置函数,它们可以实现一些常见的功能,例如:

  • subst:函数用于替换字符串中的某些字符,它接受三个参数,分别是要替换的字符、替换后的字符和原始字符串,它会返回替换后的字符串。例如:
$(info [SUBST] $(subst a,b,cat) <- (cat: a -> b)) # 将cat 中的a 替换为b,结果为cbt
$(info [SUBST] $(subst .c,.o,foo.c) <- (foo.c: .c -> .o)) # 将foo.c 中的.c 替换为.o,结果为foo.o

输出结果为:

[SUBST] cbt <- (cat: a -> b)
[SUBST] foo.o <- (foo.c: .c -> .o)
  • patsubst:函数用于根据模式替换字符串中的某些字符,它接受三个参数,分别是要替换的模式、替换后的模式和原始字符串,它会返回替换后的字符串。模式中可以使用%表示任意长度的字符。例如:
$(info [PATSUBST] $(patsubst %.c,%.o,foo.c bar.c baz.c) <- (%.c -> %.o)) 
# 将foo 开头的字符串替换为bar 开头的字符串,结果为barbar barbaz barqux
$(info [PATSUBST] $(patsubst foo%,bar%,foobar foobaz fooqux) <- (foo% -> bar%))

输出结果为:

[PATSUBST] foo.o bar.o baz.o <- (%.c -> %.o)
[PATSUBST] barbar barbaz barqux <- (foo% -> bar%)
  • strip:函数用于去除字符串中的多余空格,它接受一个参数,即原始字符串,它会返回去除空格后的字符串。例如:
$(info [STRIP] "$(strip   hello   example02)" <- 去除多余空格) # 去除多余空格,结果为hello example02

输出结果为:

[STRIP] "hello example02" <- 去除多余空格
  • wildcard:函数用于根据模式匹配文件名,它接受一个参数,即匹配模式,它会返回匹配到的文件名列表。模式中可以使用*表示任意长度的字符,?表示任意单个字符,[...]表示任意一个在括号内的字符。例如:
# 匹配所有以.c 结尾的文件名,结果为foo.c(假设当前目录下只有这个文件)
$(info [WILDCARD] $(wildcard *.c) <- 匹配所有以.c 结尾的文件名) 
# 匹配所有以foo 开头,一个任意字符,.c 结尾的文件名,结果为空(假设当前目录下没有这样的文件)
$(info [WILDCARD] $(wildcard foo?.c) <- 匹配所有以foo 开头,一个任意字符,.c 结尾的文件名) 
# 匹配所有以foo 开头,a 或b 或c,.c 结尾的文件名,结果为空(假设当前目录下没有这样的文件)
$(info [WILDCARD] $(wildcard foo[abc].c) <- 匹配所有以foo 开头,a 或b 或c,.c 结尾的文件名)

输出结果为(假设当前目录下有以下文件):

[WILDCARD] foo.c foo1.c fooa.c foo2.c <- 匹配所有以.c 结尾的文件名
[WILDCARD] foo1.c fooa.c foo2.c <- 匹配所有以foo 开头,一个任意字符,.c 结尾的文件名
[WILDCARD] fooa.c <- 匹配所有以foo 开头,a 或b 或c,.c 结尾的文件名
  • foreach:这个函数用于对列表中的每个元素执行某个操作,它接受三个参数,分别是变量名、列表和操作表达式,它会返回操作后的结果列表。例如:
$(info [FOREACH] $(foreach x,2 0 2 3 ,$(x) * 2 = $(shell expr $(x) \* 2)))

输出结果为:

[FOREACH] 2 * 2 = 4 0 * 2 = 0 2 * 2 = 4 3 * 2 = 6
  • if:这个函数用于根据条件执行不同的操作,它接受两个或三个参数,分别是条件表达式、为真时的操作表达式和为假时的操作表达式(可选),它会返回执行后的结果。例如:
# 如果Ln28 已经定义,打印Ln28 is defined,否则打印Ln28 is not defined
$(info $(if $(Ln28),Ln28 is defined,Ln28 is not defined)) 
# 如果FILE 是一个以.c 结尾的文件名,打印FILE is a C source file,否则打印FILE is not a C source file
FILE := fooa.c
$(info $(if $(filter %.c,$(FILE)),$(FILE) is a C source file,$(FILE) is not a C source file))

输出结果为(假设Ln28未定义,FILE 为fooa.c):

Ln28 is not defined
fooa.c is a C source file

自定义函数

除了内置函数,我们还可以自己定义一些函数,以实现一些特定的功能。自定义函数的一般形式为:

define 函数名
函数体
endef

函数名是一个自己取的名字,函数体是一些Makefile语句或表达式,可以使用$(1)$(2)等表示传入的参数。自定义函数可以使用call函数来调用。例如:

# 定义一个自定义函数,用于计算两个数的和
define add
$(shell expr $(1) + $(2))
endef
# 使用call 函数调用自定义函数
$(info call add:$(call add,2,0)) # 调用add 函数,传递2 和0 作为参数,结果为2
$(info call add:$(call add,2,3)) # 调用add 函数,传递2 和3 作为参数,结果为5

输出结果为:

call add:2
call add:5

总结

本文介绍了Makefile中函数的使用方法和一些常用的函数和make,m等命令的意义。通过使用函数,可以简化和优化Android.mk(Makefile)中的字符串操作,并且可以实现一些复杂的功能。Android.mk中还有很多其他的内置函数和自定义函数,有时间会继续测试补充。

相关文章
|
18天前
|
存储 监控 调度
Android系统服务:WMS、AMS相关知识
参考文献 Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析 Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析 Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析 Android窗口管理服务WindowManagerService显示窗口动画的原理分析
|
21天前
|
Java Linux Android开发
Android面试题之说说系统的启动流程(总结)
这篇文章概述了Android系统的启动流程,从Boot Rom到Zygote进程和SystemServer的启动。init进程作为用户级别的第一个进程,负责创建文件目录、初始化服务并启动Zygote。Zygote通过预加载资源和创建Socket服务,使用fork函数生成SystemServer进程。fork过程中,子进程继承父进程大部分信息但具有独立的进程ID。Zygote预加载资源以减少后续进程的启动时间,而SystemServer启动众多服务并最终开启Launcher应用。文中还讨论了为何从Zygote而非init或SystemServer fork新进程的原因。
27 2
|
7天前
|
Java Maven Android开发
安卓项目使用阿里云镜像加速构建过程
安卓项目使用阿里云镜像加速构建过程
8 0
|
10天前
|
安全 搜索推荐 Android开发
探索安卓和iOS系统的优劣与特点
在移动操作系统领域,安卓和iOS一直是最热门的两个选择。本文将探讨安卓和iOS系统的优劣与特点,帮助读者更好地了解这两个操作系统,并为选择合适的移动设备提供参考。
14 0
|
21天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的安卓的微博客系统的详细设计和实现
基于SpringBoot+Vue+uniapp的安卓的微博客系统的详细设计和实现
14 0
|
24天前
|
机器学习/深度学习 搜索推荐 Android开发
在安卓应用开发中,构建高效的用户界面是至关重要的一环
【6月更文挑战第10天】本文是关于构建高效安卓用户界面的指南,分为设计原则和技巧两部分。设计原则包括一致性、简洁性和可访问性,强调遵循安卓系统规范、保持界面简洁及考虑不同用户需求。技巧方面,建议合理布局、优化图标和图片、使用动画效果、提供个性化设置以及优化性能。随着技术发展,开发者需关注AI和机器学习,以创新应用体验,适应用户需求变化。
27 0
|
28天前
|
前端开发 Java API
Android系统中读写和显示图片
Android系统中读写和显示图片
14 0
|
28天前
|
编解码 缓存 Android开发
构建高效的Android应用:从内存优化到响应式设计
【5月更文挑战第37天】 在竞争激烈的移动应用市场中,一个高效、流畅的Android应用是吸引和保留用户的关键。本文将深入探讨构建高效Android应用的多个关键方面,包括内存优化策略、布局性能和响应式设计原则。我们将通过具体的技术实践和案例分析,揭示如何提升应用性能,减少资源消耗,并确保在不同设备上的兼容性和用户体验一致性。
|
监控 Android开发
【Android 逆向】函数拦截 ( GOT 表拦截 与 插桩拦截 | 插桩拦截简介 | 插桩拦截涉及的 ARM 和 x86 中的跳转指令 )
【Android 逆向】函数拦截 ( GOT 表拦截 与 插桩拦截 | 插桩拦截简介 | 插桩拦截涉及的 ARM 和 x86 中的跳转指令 )
143 0
【Android 逆向】函数拦截 ( GOT 表拦截 与 插桩拦截 | 插桩拦截简介 | 插桩拦截涉及的 ARM 和 x86 中的跳转指令 )
|
存储 Android开发
【Android 逆向】函数拦截 ( GOT 表数据结构分析 | 函数根据 GOT 表进行跳转的流程 )
【Android 逆向】函数拦截 ( GOT 表数据结构分析 | 函数根据 GOT 表进行跳转的流程 )
138 0
【Android 逆向】函数拦截 ( GOT 表数据结构分析 | 函数根据 GOT 表进行跳转的流程 )