背景:
经常在在对系统进行相关修改的时候,需要阅读或者修改Android.mk,以前我们学习或者编写mk文件,一般都是定义一个固定的目标模板,比如定义一个apk目标,定义一个native程序目标等。
比如下面apk编译模板:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# 设置模块名为myapp
LOCAL_MODULE := myapp
# 添加需要编译的Java源文件
LOCAL_SRC_FILES := $(wildcard *.java)
# 添加需要编译的资源文件
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
# 设置输出APK的路径和名称
LOCAL_PACKAGE_NAME := myapp
LOCAL_PACKAGE_OUTPUT_FILE := $(LOCAL_PATH)/$(LOCAL_PACKAGE_NAME).apk
# 设置系统权限和目录
LOCAL_CERTIFICATE := platform
LOCAL_PRIVATE_PLATFORM_APIS := true
# 设置是否编译在 priv-app,如果没有指定目录默认为 system/priv-app
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_PACKAGE)
当时属于没有去深入mk相关的一些语法和函数,大部分需要用时候都是依葫芦画瓢方式,今天我们来对部分常用的mk基础知识做一个讲解,这样在后需要用到mk就知道其原理了。
mk基本符号说明
赋值部分:
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值
而 = 与 := 的区别在于,= 会在makefile 展开后再决定变量的值,即最后被指定的值
x = foo
y = $(x) bar
x = xyz
在上例中,y的值将会是 xyz bar ,而不是 foo bar 。
单个 $ —— 变量引用或函数调用
作用:用于引用变量或调用函数。
语法:
$(VAR_NAME) 或 ${VAR_NAME} —— 引用变量 VAR_NAME 的值。
$(call func,arg1,arg2) —— 调用自定义函数 func 并传入参数。
双 $$ —— 转义 $,使其在 Shell 命令中保留
作用:在 $(shell …) 或
(
f
o
r
e
a
c
h
.
.
.
)
等需要执行
S
h
e
l
l
命令的上下文中,
(foreach ...) 等需要执行 Shell 命令的上下文中,
(foreach...)等需要执行Shell命令的上下文中,$ 会被转义成单个 $,避免被 Make 解析。
常见场景:
在 $(shell …) 中使用环境变量:
BUILD_TIME := $(shell date +%Y%m%d) # 直接使用 Shell 命令
JAVA_HOME := $(shell echo $$JAVA_HOME) # 使用 $$ 获取 Shell 环境变量
为什么 $$ 在 $(shell …) 里是必要的?
因为 $(shell …) 会执行 Shell 命令,而 $VAR 在 Shell 中表示变量。如果直接写 $PATH,Make 会尝试解析 PATH 变量,而不是传递给 Shell。
常见一些方法和关键字说明
call关键字
宏函数调用
$(call macro,param1,param2)会展开宏定义,并将参数$1、$2等替换为传入的值
define greet
$(info Hello, $1!)
endef
$(call greet,World) # 输出"Hello, World!"
比如最常见的的
LOCAL_PATH := $(call my-dir)
调用获取当前mk的路径,这里的my-dir实际是一个宏函数,定义在如下文件:
build/core/definitions.mk
###########################################################
## Retrieve the directory of the current makefile
## Must be called before including any other makefile!!
###########################################################
# Figure out where we are.
define my-dir
$(strip \
$(eval LOCAL_MODULE_MAKEFILE := $$(lastword $$(MAKEFILE_LIST))) \
$(if $(filter $(BUILD_SYSTEM)/% $(OUT_DIR)/%,$(LOCAL_MODULE_MAKEFILE)), \
$(error my-dir must be called before including any other makefile.) \
, \
$(patsubst %/,%,$(dir $(LOCAL_MODULE_MAKEFILE))) \
) \
)
endef
eval 关键字
eval 是 GNU Make 的关键字,主要用于动态生成 Makefile 代码,其核心作用与典型用法如下:
动态代码生成
eval 会将传入的字符串解析为 Makefile 代码并执行,常用于构建过程中动态生成编译规则或变量定义
define add_module
$(eval include $(CLEAR_VARS)) # 动态插入清除变量的操作
$(eval LOCAL_MODULE := $1)
$(eval include $(BUILD_SHARED_LIBRARY))
endef
$(call add_module,foo)
常见其他函数说明
include部分
基础语法
include $(FILENAME)
用于包含指定的Makefile文件
忽略错误
前缀-表示若文件不存在则忽略错误继续编译:
-include $(PATH_TO_FILE) # 文件缺失时不报错
常用预定义规则
$(CLEAR_VARS):清除模块变量(如LOCAL_MODULE),需在每个模块开头使用。
$(BUILD_XXX):指定编译类型,如:
include $(BUILD_SHARED_LIBRARY) # 编译动态库
include $(BUILD_STATIC_LIBRARY) # 编译静态库
include $(BUILD_EXECUTABLE) # 编译可执行程序
strip部分
strip 函数是一个用于处理字符串的函数。它主要用于去除字符串的空格或特定字符,常用于构建或定义变量时进行字符串清理。
语法
strip(param)
参数: param 表示要处理的输入字符串。
返回值: 返回去除字符串两端空白字符后的字符串。
功能
strip 函数删除输入字符串开头和结尾的空格(包括制表符和换行符),并保留中间的空格。
这个函数通常用于确保字符串在使用前没有多余的空格,避免在构建过程中出现意外的格式问题。
案例
MY_VAR := Hello, World!
STRIPPED_VAR := $(strip $(MY_VAR))
本身是前面有空格的 Hello, World! ,经过strip后没有空格直接输出Hello, World!
filter和filter-out部分
filter 函数用于从一组字符串中筛选出符合特定模式的字符串。这对于处理和管理编译文件或目标文件非常有用。
语法
filter(PATTERN1 PATTERN2 ... , STRING1 STRING2 ...)
参数:
PATTERN1 PATTERN2 …:一系列模式,可以使用通配符(如 * 和 ?)。
STRING1 STRING2 …:要筛选的字符串列表。
返回值: 返回与所有模式匹配的字符串。
功能
filter 函数遍历指定的字符串列表,并返回所有匹配提供模式的字符串。
如果没有任何字符串与模式匹配,返回空字符串。
案例
#案例1
FILES := file1.o file2.o file3.o fileA.o fileB.o
# 筛选出以 file1 和 fileA 开头的文件
FILTERED_FILES := $(filter file1.o fileA.o, $(FILES))
#案例2
FILES := src/file1.cpp src/file1.h src/file2.cpp src/fileA.java
# 筛选出以 file1 结尾的文件
FILTERED_FILES := $(filter %file1.*, $(FILES))
filter-out
作用其实和filter恰恰相反,filter-out函数用于从一组字符串中删除特定模式的字符串。
语法
$(filter-out key1 key2,$(VAR))
把VAR中包含的key1和key2过滤掉,其余的全部保留。
对比案例如下:
1.如:VAR = a b c d, key1=a,key2=b
则:
$(filter key1 key2,$(VAR)) ===> a b
$(filter-out key1 key2,$(VAR)) ====>c d
2.如:VAR = a b c d, key1=a,key2=e
则:
$(filter key1 key2,$(VAR)) ===> a
$(filter-out key1 key2,$(VAR)) ====> b c d
ifeq 和 ifneq
ifeq 和 ifneq 是在 Makefile 中用于条件判断的指令,它们允许根据给定条件执行不同的代码块。这两个指令主要用于根据变量的值执行不同的操作。
ifeq语法
ifeq (条件1, 条件2)
# 条件成立时执行的代码
endif
案例
VERSION := 1.0
ifeq ($(VERSION), 1.0)
$(info Version is 1.0)
endif
ifneq语法
ifneq (条件1, 条件2)
# 条件不成立时执行的代码
endif
案例
VERSION := 1.0
ifneq ($(VERSION), 2.0)
$(info Version is not 2.0)
endif
其他更多mk相关可以看官网:
https://developer.android.google.cn/ndk/guides/android_mk#local_src_files
更多framework实战开发干货,请关注下面“千里马学框架”