LV130-自定义函数
一、自定义函数
1. 简介
在 Makefile 中,“自定义函数”本质上是通过 define、endef 关键字创建的 多行变量,它也是命令的集合。可以配合 $(call ...) 函数来调用。它允许你封装复杂的命令或文本处理逻辑,并可以接受参数。
2. 定义函数
和命令包的定义格式相同:
# 定义一个名为 my_function 的函数,它接受两个参数
define my_function
@echo "func name is $(0)\n"
@echo "first param is $(1)\n"
@echo "second param is $(2)\n"
@echo "rule's target is: $(@)"
endef其中 $(0) 是函数名,$(1) 是第 1 个参数,$(2) 是第 2 个参数。$(@) 就是之前学习的自动变量。
3. 调用自定义函数
使用下面的语法格式来调用函数:
$(call <function_name>, <arg1>, <arg2>, ...)在函数体内,通过 $(1), $(2), $(3)... 来访问传入的参数。$(@) 代表所有参数。
4. 与命令包的区别
虽然命令包和函数都可以实现代码复用,但它们有重要区别:
- 函数:返回一个值,用于变量赋值或函数调用
- 命令包:包含多条命令,用于目标规则中的命令执行
命令包主要用于组织目标规则中的命令,而函数主要用于处理文本或返回值。
5. 使用示例
define my_function
@echo "func name is $(0)\n"
@echo "first param is $(1)\n"
@echo "second param is $(2)\n"
@echo "rule's target is: $(@)"
endef
demo:
# 调用自定义函数,并传递两个参数
$(call my_function, Hello, World)我们会得到以下打印信息:
sumu@virtual-machine:~/hk/alpha$ make
# 调用自定义函数,并传递两个参数
func name is my_function\n
first param is Hello\n
second param is World\n
all param is: demo二、函数中的自动变量
$(@), $<, $^ 等是 Makefile 规则的自动变量,在函数体内使用的话依然是自动变量且由当前规则决定。我们直接来看一个示例:
# 定义一个使用自动化变量的函数
# $@ - 当前规则的目标文件名
# $< - 当前规则的第一个依赖项
# $^ - 当前规则的所有依赖项
define build_rule_info
@echo "=== 函数打印信息 ==="
@echo "目标文件: $(@)"
@echo "第一个依赖: $(<)"
@echo "所有依赖: $(^)"
@echo "依赖数量: $(words $(^))"
@echo ""
endef
# 主要目标,依赖前三个文件
main_target: file1.txt file2.txt file3.txt
$(call build_rule_info)
# 创建一些测试文件
file1.txt:
@echo "这是文件1"
file2.txt:
@echo "这是文件2"
file3.txt:
@echo "这是文件3"我们将会看到如下打印信息:
sumu@virtual-machine:~/hk/alpha$ make
这是文件1
这是文件2
这是文件3
=== 函数打印信息 ===
目标文件: main_target
第一个依赖: file1.txt
所有依赖: file1.txt file2.txt file3.txt
依赖数量: 3三、应用实例
我们参考uboot或者kernel源码中的makefile,我们编写一个生成时间戳头文件的示例。
1. 源码编写
1.1 Kbuild.include
# filechk_timestamp_autogenerated.h中 只是输出到标准输出,真正的文件写入是通过调用这个函数的规则中的重定向操作符 > 完成的。括号 () 确保所有命令的输出被收集起来,然后一次性重定向到文件。filechk函数中的 > $@.tmp - 这才是写入文件的关键! 它将整个子shell的输出重定向到临时文件
define filechk_timestamp_autogenerated.h
(\
LC_ALL=C echo '#ifndef __TIMESTAMP_AUTOGENERATED_H__'; \
LC_ALL=C echo '#define __TIMESTAMP_AUTOGENERATED_H__'; \
LC_ALL=C date +'#define DEMO_DATE "%b %d %C%y"'; \
LC_ALL=C date +'#define DEMO_TIME "%T"'; \
LC_ALL=C date +'#define DEMO_TZ "%z"'; \
LC_ALL=C date +'#define DEMO_DMI_DATE "%m/%d/%Y"'; \
LC_ALL=C date +'#define DEMO_BUILD_DATE 0x%Y%m%d'; \
LC_ALL=C echo '#endif /* __TIMESTAMP_AUTOGENERATED_H__ */'; \
)
endef
define filechk
$(Q)set -e; \
: ' CHK $@'; \
mkdir -p $(dir $@); \
$(filechk_$(1)) < $< > $@.tmp; \
if [ -r $@ ] && cmp -s $@ $@.tmp; then \
rm -f $@.tmp; \
else \
: ' UPD $@'; \
mv -f $@.tmp $@; \
fi
endef这里的 LC_ALL=C 是国际化设置。强制使用标准 C 语言环境,避免因为系统是中文或法文导致生成的头文件内容不同,从而影响编译哈希值。
外层为什么要有一个括号?
(1)它将所有命令作为一个整体执行:括号创建了一个子shell,里面的所有命令都会在这个子shell中顺序执行。如果去掉括号,每个 echo 和 date 命令都会在独立的进程中执行,这会导致问题。
(2)确保输出顺序正确:在子shell中,所有命令的输出会按顺序收集,然后一次性输出。如果没有括号,多个命令的输出可能会交错或顺序错乱。
1.2 Makefile
-include Kbuild.include
# 这里可以指定文件目录,例如 a/timestamp_autogenerated.h,则会自动创建a目录
timestamp_h := timestamp_autogenerated.h
srctree := .
# Beautify output
# ---------------------------------------------------------------------------
ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif
# target
# ---------------------------------------------------------------------------
all: $(timestamp_h)
# 生成时间和版本信息头文件
$(timestamp_h): $(srctree)/Makefile FORCE
$(call filechk,timestamp_autogenerated.h)
PHONY += clean
clean:
@rm -rf $(timestamp_h)
# PHONY 伪目标
# ---------------------------------------------------------------------------
PHONY += FORCE
FORCE:
# Declare the contents of the .PHONY variable as phony. We keep that
# information in a variable so we can use it in if_changed and friends.
.PHONY: $(PHONY)2. 运行效果
可以执行下面的命令:
make # 静默输出
make V=1 # 会显示完整的命令最后就会发现生成了一个名为 timestamp_autogenerated.h 的文件,里面是生成的时间。