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中还有很多其他的内置函数和自定义函数,有时间会继续测试补充。

相关文章
|
12天前
|
算法 JavaScript Android开发
|
14天前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
36 2
|
13天前
|
安全 搜索推荐 程序员
深入探索Android系统的碎片化问题及其解决方案
在移动操作系统的世界中,Android以其开放性和灵活性赢得了广泛的市场份额。然而,这种开放性也带来了一个众所周知的问题——系统碎片化。本文旨在探讨Android系统碎片化的现状、成因以及可能的解决方案,为开发者和用户提供一种全新的视角来理解这一现象。通过分析不同版本的Android系统分布、硬件多样性以及更新机制的影响,我们提出了一系列针对性的策略,旨在减少碎片化带来的影响,提升用户体验。
|
13天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
15天前
|
安全 Android开发 iOS开发
安卓系统与iOS系统的比较####
【10月更文挑战第26天】 本文将深入探讨安卓(Android)和iOS这两大主流移动操作系统的各自特点、优势与不足。通过对比分析,帮助读者更好地理解两者在用户体验、应用生态、系统安全等方面的差异,从而为消费者在选择智能手机时提供参考依据。无论你是技术爱好者还是普通用户,这篇文章都将为你揭示两大系统背后的故事和技术细节。 ####
36 0
|
监控 Android开发
【Android 逆向】函数拦截 ( GOT 表拦截 与 插桩拦截 | 插桩拦截简介 | 插桩拦截涉及的 ARM 和 x86 中的跳转指令 )
【Android 逆向】函数拦截 ( GOT 表拦截 与 插桩拦截 | 插桩拦截简介 | 插桩拦截涉及的 ARM 和 x86 中的跳转指令 )
164 0
【Android 逆向】函数拦截 ( GOT 表拦截 与 插桩拦截 | 插桩拦截简介 | 插桩拦截涉及的 ARM 和 x86 中的跳转指令 )
|
存储 Android开发
【Android 逆向】函数拦截 ( GOT 表数据结构分析 | 函数根据 GOT 表进行跳转的流程 )
【Android 逆向】函数拦截 ( GOT 表数据结构分析 | 函数根据 GOT 表进行跳转的流程 )
156 0
【Android 逆向】函数拦截 ( GOT 表数据结构分析 | 函数根据 GOT 表进行跳转的流程 )
|
存储 缓存 Java
【Android 逆向】函数拦截 ( 使用 cache_flush 系统函数刷新 CPU 高速缓存 | 刷新 CPU 高速缓存弊端 | 函数拦截推荐时机 )
【Android 逆向】函数拦截 ( 使用 cache_flush 系统函数刷新 CPU 高速缓存 | 刷新 CPU 高速缓存弊端 | 函数拦截推荐时机 )
163 0
【Android 逆向】函数拦截 ( 使用 cache_flush 系统函数刷新 CPU 高速缓存 | 刷新 CPU 高速缓存弊端 | 函数拦截推荐时机 )
|
Android开发
【Android 逆向】函数拦截原理 ( 通过修改 GOT 全局偏移表拦截函数 | 通过在实际被调用的函数中添加跳转代码实现函数拦截 )
【Android 逆向】函数拦截原理 ( 通过修改 GOT 全局偏移表拦截函数 | 通过在实际被调用的函数中添加跳转代码实现函数拦截 )
216 0
【Android 逆向】函数拦截原理 ( 通过修改 GOT 全局偏移表拦截函数 | 通过在实际被调用的函数中添加跳转代码实现函数拦截 )
|
Java Linux Android开发
【Android 逆向】函数拦截原理 ( 可执行程序基本结构 | GOT 全局偏移表 | 可执行程序函数调用步骤 )
【Android 逆向】函数拦截原理 ( 可执行程序基本结构 | GOT 全局偏移表 | 可执行程序函数调用步骤 )
245 0
【Android 逆向】函数拦截原理 ( 可执行程序基本结构 | GOT 全局偏移表 | 可执行程序函数调用步骤 )