在这篇博客中,我将介绍Android.mk详解中的其中一个部分【函数】和make/m等命令概念的区分,Android.mk文件中使用Makefile中函数的概念和用法,以及一些常用的内置函数和自定义函数。通过使用函数,可以简化和优化Makefile中的字符串操作,可以实现一些复杂的功能。我会附上详细的测试代码和测试结果。。
我看到Android.mk里面的函数,最开始概念模糊的时候,闪过一个疑问:
Android.mk 为什么能使用Makefile函数?
查阅资料了解到:
需要先搞清楚Android.mk
和Makefile
的差异,Android.mk
是早期Android构建系统的一部分,能够执行Android.mk
的工具叫make
,make
是用来搞自动化编译和构建的。那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 是不是立马想到了(如 mmm
、mm
、m
等)等命令来执行?
但不然,我们可以使用 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
? 什么时候时候(如 mmm
、mm
、m
等)等命令来执行?
其实可以这样简单快速区分(但不完全是这么理解啊),
刚开始理解,死记构建一个目标模块用( mmm
、mm
、m
),这个目标比如是指include $(BUILD_EXECUTABLE)
,在我example01的测试代码中,这个就是为了构建一个exec01
可执行文件,可执行文件就是一个目标!
小节一下:
- 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
- m:
- 这是一个快捷命令,等同于在 源代码的根目录下运行
make
。 - 它用于构建整个 Android 系统。
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk$ m #m == make
- mm:
- 这个命令专门用于构建当前目录下的模块。
- 它是为了方便在特定目录下快速构建和测试模块。
ln28@ln28-pc:~/sourcecode/rk_android12.0_sdk/vendor/customize/demo/example02$ mm
- 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中还有很多其他的内置函数和自定义函数,有时间会继续测试补充。