[TOC]
scripts 用于构建脚本的工具
scripts_basic 用于构建基本脚本的工具 参考
uboot 和 Linux kernel 的主 Makefile 里面,都有下面这段话:
1 2 3 4 PHONY += scripts_basic scripts_basic: $(Q) $(MAKE) $(build) =scripts/basic $(Q) rm -f .tmp_quiet_recordmcount
展开后就是:
1 make -f ./scripts/Makefile.build obj=scripts/basic
$(build)的展开 主 Makefile 里面,有一个非常重要的 include:
1 include scripts/Kbuild.include
这是一个头文件,相当于把 Kbuild.include
的内容直接写到主 Makefile 里。Kbuild.include
代码片段:
1 2 3 4 5 build := -f $(srctree)/scripts/Makefile.build obj
这句话的实质,是变量的字符串展开;就是说 build 被当做了 Kbuild 的一个保留的字符串变量。 所以 $(Q)$(MAKE) $(build)=scripts/basic
的展开,就是把$(build)
用 := 后的字符串替换
Kbuild 为不同的编译目标生成编译规则。 Kbuild 中,几乎或者全部的 .o 都是由 Makefile.build 来完成编译的。 Makefile.build 会为不同类型的编译对象,指定不同的规则。类似于大家喜闻乐见的:
1 gcc -c -o hello.o hello.c
但是 Kbuild 追求的是批量化编译,所以指定规则的过程很复杂,需要认真分析下。 这篇文章,只分析 hostprogs 相关的规则生成和编译。 hostprogs 是在编译过程中主机所用到的一些工具;hostprogs 编译的输出件是运行在主机上的,最后不会被链接到目标文件中。
过滤掉 Makefile.build 中和 hostprogs 无关的代码,得:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 src := $(obj) PHONY := __build __build: ... include scripts/Kbuild.include kbuild-dir := $(if $(filter /%,$(src) ) ,$(src) ,$(srctree) /$(src) ) kbuild-file := $(if $(wildcard $(kbuild-dir ) /Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file)include scripts/Makefile.libhostprogs := $(sort $(hostprogs) ) ifneq ($(hostprogs) ,)include scripts/Makefile.hostendif ... __build: $(if $(KBUILD_BUILTIN) , $(targets-for-builtin)) \ $(if $(KBUILD_MODULES) , $(targets-for-modules) ) \ $(subdir-ym) $(always-y) @: PHONY += FORCE FORCE: ... .PHONY : $(PHONY)
找到子目录中的 Makefile 关于 $(kbuild-file) 的分析
1 2 3 4 5 6 7 8 kbuild-dir := $(if $(filter /%,$(src) ) ,$(src) ,$(srctree) /$(src) ) kbuild-file := $(if $(wildcard $(kbuild-dir ) /Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file)
这些语句,相当于 include 了scripts/basic/Makefile。 这个文件就一句话:
1 2 hostprogs-always-y += fixdep
使用 Makefile.lib 对编译目标进行分类和整理 scripts/Makefile.lib 会对各种编译目标进行转换、抽象和提取。 编译目标的种类有很多,这里只追踪 hostprogs 相关的。
1 2 3 4 hostprogs += $(hostprogs-always-y) $(hostprogs-always-m) always-y += $(hostprogs-always-y) $(hostprogs-always-m) always-y := $(addprefix $(obj) /,$(always-y) )
这样,可以获得
1 2 hostprogs = fixdep always-y = scripts/basic/fixdep
是的,虽然 Makefile.build 这个文件很长,但是真正和 scripts/basic 相关的,就上面 3 行代码。
Makefile.host 所做的工作 因为 hostprogs 不为空,所以引入了另一个包含
1 2 3 4 ifneq ($(hostprogs) ,)include scripts/Makefile.hostendif
scripts/Makefile.host
这个文件会将 $(hostprogs)
转换为 $(host-csingle)
、$(host-cmulti)
等目标。这里需要介绍下 host-csingle
、host-cmulti
、host-cobjs
、host-cxxmulti
和 host-cxxobjs
这几个变量。这是 Makefile.host
所能处理的几类编译目标。Makefile.host
会为这几类目标生成编译规则。说到底,Kbuild
的这一套机制,目的就是为不同的编译目标生成编译规则。
host-cobjs 实例 这里只是举例,在编译 scripts/basic
时是不会用到 host-cobjs
类型的目标的。介绍 host-cobjs,只是为后面分析 host-csingle 做铺垫。以 qconf 为例,scripts/kconfig/Makefile 中的代码段:
1 2 3 4 5 6 7 common-objs := confdata.o expr.o lexer.lex.o parser.tab.o preprocess.o \ symbol.o util.o hostprogs += qconf qconf-cxxobjs := qconf.o qconf-moc.o qconf-objs := images.o $(common-objs)
然后可得:hostprogs = qconf
,也许还有其他的,此处忽略;qconf-objs = images.o confdata.o expr.o lexer.lex.o parser.tab.o preprocess.o symbol.o util.o
Makefile.host 对 host-cobjs 的处理
1 2 3 4 5 6 7 8 9 host-cobjs := $(sort $(foreach m,$(hostprogs) ,$($(m) -objs) )) host-cobjs := $(addprefix $(obj) /,$(host-cobjs) ) ```` foreach 的作用,是将 `$(hostprogs) ` 中的变量逐个取出,然后带入到 `$($(m) -objs)) 中即得到`$(qconf-objs)`;注意,`$(qconf-objs) `还会被展开一次,即得到`images.o confdata.o expr.o lexer.lex.o parser.tab.o preprocess.o symbol.o util.o` 所以,语句1 的结果是: ```makefile host-cobjs := images.o confdata.o expr.o lexer.lex.o parser.tab.o preprocess.o symbol.o util.o
语句2 是为上面的这些 .o 添加目录前缀。所以 $(host-cobjs)
实际上是若干 .c 对应的 .o 的集合。当为 $(host-cobjs)
制定好规则以后,上面的 images.o confdata.o ...
所对应的 images.c confdata.c ...
均会批量化的按照制定的规则进行编译。
1 2 3 $(host-cobjs): $(obj) /%.o: $(src) /%.c FORCE $(call if_changed_dep,host-cobjs)
if_changed_dep
,的这个逗号后面,一定不要加空格什么的;或者说 call 的用法就这样,逗号后面不要加不相关的字符;另外,对应的 cmd_cpp_lds 等命令,一定用延时赋值“=”,而不是直接赋值“:=”,因为:如果命令中用到 $@
或者 $<
时,延时赋值可以展开,直接赋值不会展开。一定要注意!!!!!
host-csingle 实例 1 2 3 4 5 6 host-csingle := $(foreach m,$(hostprogs) , \ $(if $($(m) -objs) $($(m) -cxxobjs),,$(m) )) host-csingle := $(addprefix $(obj) /,$(host-csingle) )
此处 hostprogs = fixdep
,第一次带入得 $(fixdep-objs)
和 $(fixdep-cxxobjs)
,因为它们两个展开后,均为空,所以语句1 的返回结果就是 $host-csingle) = fixdep
;语句2 为 fixdep 添加了前缀,$(host-csingle) = scripts/basic/fixdep
;所以,$(host-csingle)
指的是那些由单个 .c 编译而成的可执行文件,比如说 fixdep
;Makefile.host
为host-csingle
制定了编译规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 quiet_cmd_host-csingle = HOSTCC $@ cmd_host-csingle = $(HOSTCC) $(hostc_flags) $(KBUILD_HOSTLDFLAGS) -o $@ $< \ $(KBUILD_HOSTLDLIBS) $(HOSTLDLIBS_$(@F)) $(host-csingle): $(obj) /%: $(src) /%.c FORCE $(call if_changed_dep,host-csingle)
再回到 scripts/Makefile.build Makefile.host 为目标制定了规则。但是,制定规则不代表调用规则。 那 scripts_basic 的规则是怎么被调用的呢? Makefile.build 的默认编译目标是 __build:
1 2 3 4 5 6 7 8 PHONY := __build __build: ... __build: $(if $(KBUILD_BUILTIN) , $(targets-for-builtin)) \ $(if $(KBUILD_MODULES) , $(targets-for-modules) ) \ $(subdir-ym) $(always-y) @:
其实,这里有个难以理解和追踪的地方了: __build 中的有效依赖是 $(always-y)
,但是搜索编整个 Makefile 工程都找不到对 $(always-y)
目标的编译——没有为 $(always-y)
设计编译规则。 那么,目标是怎么被编译的呢? 前面 scripts/Makefile.host 中得到了$(host-csingle)
,虽然 __build 中并没有包含 $(host-csingle)
,但是 $(always-y)
和 $(host-csingle)
包含了相同的目标 scripts/basic/fixdep。 所以 __build 对 $(always-y)
的依赖就是对 scripts/basic/fixdep 的依赖。 而 scripts/basic/fixdep 的编译规则已经被 $(host-csingle)
制定了。
dtc 用于生成设备树二进制文件 具体分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 $ make scripts V=1 CROSS_COMPILE=arm-none-eabi- ARCH=arm stm32h750-art-pi_defconfig set -e; \for i in scripts stm32h750-art-pi_defconfig; do \ make -f ./Makefile $i ; \ done make -f ./Makefile syncconfig make -f ./scripts/Makefile.build obj=scripts/basic cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -o scripts/basic/fixdep scripts/basic/fixdep.c rm -f .tmp_quiet_recordmcountmake -f ./scripts/Makefile.build obj=scripts/kconfig syncconfig scripts/kconfig/conf --syncconfig Kconfig make -f ./scripts/Makefile.autoconf || \ { rm -f include/config/auto.conf; false ; } if [ -d arch /arm/mach-stm32h7/include/mach ]; then \ dest=../../mach-stm32h7/include/mach; \ else \ dest=arch-stm32h7; \ fi ; \ln -fsn $dest arch /arm/include/asm/archset -e; mkdir -p include/; (echo "/* Automatically generated - do not edit */" ; echo \#define CFG_BOARDDIR board/st/stm32h750-art-pi; echo \#include \<configs/"stm32h750-art-pi" .h\> ; echo \#include \<asm/config.h\>; echo \#include \<linux/kconfig.h\>; echo \#include \<config_fallbacks.h\>;) < scripts/Makefile.autoconf > include/config.h.tmp; if [ -r include/config.h ] && cmp -s include/config.h include/config.h.tmp; then rm -f include/config.h.tmp; else : ' UPD include/config.h' ; mv -f include/config.h.tmp include/config.h; fi arm-none-eabi-gcc $(c_flags) -DDO_DEPS_ONLY -dM include/config.h > u-boot.cfg.tmp && { grep 'define CONFIG_' u-boot.cfg.tmp | sed '/define CONFIG_IS_ENABLED(/d;/define CONFIG_IF_ENABLED_INT(/d;/define CONFIG_VAL(/d;' > u-boot.cfg; rm u-boot.cfg.tmp; } || { rm u-boot.cfg.tmp; false ; } sed -n -f ./tools/scripts/define2mk.sed u-boot.cfg | while read line; do if [ -n "" ] || ! grep -q "${line%=*} =" include/config/auto.conf; then echo "$line " ; fi ; done > include/autoconf.mk arm-none-eabi-gcc -x c -DDO_DEPS_ONLY -M -MP $(c_flags) -MQ include/config/auto.conf include/config.h > include/autoconf.mk.dep || { rm include/autoconf.mk.dep; false ; } touch include/config/auto.confmake -f ./scripts/Makefile.build obj=scripts/basic rm -f .tmp_quiet_recordmcountif test "./scripts/dtc/dtc" = "./scripts/dtc/dtc" ; then \ make -f ./scripts/Makefile.build obj=scripts/dtc; \ else \ if ! ./scripts/dtc/dtc -v >/dev/null; then \ echo '*** Failed to check dtc version: ./scripts/dtc/dtc' ; \ false ; \ else \ if test "010406" -lt 010406; then \ echo '*** Your dtc is too old, please upgrade to dtc 010406 or newer' ; \ false ; \ else \ if [ -n "" ]; then \ if ! echo "import libfdt" | python3 2>/dev/null; then \ echo '*** pylibfdt does not seem to be available with python3' ; \ false ; \ fi ; \ fi ; \ fi ; \ fi ; \ fi make -f ./scripts/Makefile.build obj=scripts make -f ./scripts/Makefile.build obj=scripts/basic rm -f .tmp_quiet_recordmcount
scripts/Kbuild 用于构建脚本的 Kbuild 文件 echo-cmd 用于控制命令的回显
调用 escsq 函数,对 $(quiet)cmd_$(1)
的内容进行转义处理
$(echo-why) 是一个变量,通常用于附加额外的回显信息
1 2 echo-cmd = $(if $($(quiet) cmd_$(1) ),\ echo ' $(call escsq,$($(quiet) cmd_$(1) ))$(echo-why)';)
filechk 用于检查文件是否改变
在dts/upstream/scripts/Kbuild.include中定义
规则的输出应写入标准输出,新文件的内容将与现有文件进行比较。如果文件不存在,则创建新文件;如果内容不同,则使用新文件;如果内容相同,则不进行更改,也不更新时间戳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 define filechk $(Q) set -e; \ $(kecho) ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ $(kecho) ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef define filechk_config_h (echo "/* Automatically generated - do not edit */" ; \ echo \ $(if $(CONFIG_SYS_CONFIG_NAME) ,echo \#include \<configs/$(CONFIG_SYS_CONFIG_NAME) .h\> ;) \ echo \ echo \ echo \ endef
fixdep 用于处理依赖关系的工具
fixdep
是一个用于处理依赖关系的工具,通常在构建系统中使用。它解析依赖文件(depfile
),生成目标文件(target
)的依赖关系,并输出相应的命令行(cmdline
)
调用流程
if_changed_dep
:如果目标文件的依赖关系发生变化,则执行指定的命令行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 if_changed_dep = $(if $(strip $(any-prereq) $(arg-check) ), \ @set -e; \ echo "cmd_$(1): $(cmd_$(1))" ; \ echo 'call fixdep: scripts/basic/fixdep $(depfile) $@ "$(make-cmd)" ';\ scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\ rm -f $(depfile) ; \ mv -f $(dot-target).tmp $(dot-target).cmd, @:)
1 2 3 4 5 6 $ make scripts V=1 CROSS_COMPILE=arm-none-eabi- ARCH=arm stm32h750-art-pi_defconfig # $(host-csingle) 指定了编译规则 在scripts/Makefile.host中定义了编译规则 # 而$(always-y) 从include $(kbuild-file)导入,即scripts/basic/Makefile cmd_host-csingle: cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -o scripts/basic/fixdep scripts/basic/fixdep.c cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -o scripts/basic/fixdep scripts/basic/fixdep.c call fixdep: scripts/basic/fixdep scripts/basic/.fixdep.d scripts/basic/fixdep "cc -Wp,-MD,scripts/basic/.fixdep.d -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu11 -o scripts/basic/fixdep scripts/basic/fixdep.c "
构建 1 2 3 4 5 6 7 8 9 10 hostprogs-y := fixdep always := $(hostprogs-y) $(addprefix $(obj) /,$(filter -out fixdep,$(always) ) ): $(obj) /fixdep
使用方法 1 Usage: fixdep <depfile> <target> <cmdline>
<depfile>
:依赖文件,包含目标文件的依赖关系。
<target>
:目标文件,通常是需要编译的源文件。
<cmdline>
:命令行,用于编译目标文件的命令。
源代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 static void xprintf (const char *format, ...) { va_list ap; int ret; va_start(ap, format); ret = vprintf (format, ap); if (ret < 0 ) { perror("fixdep" ); exit (1 ); } va_end(ap); } static void *read_file (const char *filename) { struct stat st ; int fd; char *buf; fd = open(filename, O_RDONLY); if (fd < 0 ) { fprintf (stderr , "fixdep: error opening file: " ); perror(filename); exit (2 ); } if (fstat(fd, &st) < 0 ) { fprintf (stderr , "fixdep: error fstat'ing file: " ); perror(filename); exit (2 ); } buf = malloc (st.st_size + 1 ); if (!buf) { perror("fixdep: malloc" ); exit (2 ); } if (read(fd, buf, st.st_size) != st.st_size) { perror("fixdep: read" ); exit (2 ); } buf[st.st_size] = '\0' ; close(fd); return buf; } static void parse_dep_file (char *m, const char *target) { char *p; int is_last, is_target; int saw_any_target = 0 ; int is_first_dep = 0 ; void *buf; while (1 ) { while (*m == ' ' || *m == '\\' || *m == '\n' ) m++; if (!*m) break ; p = m; while (*p && *p != ' ' && *p != '\\' && *p != '\n' ) p++; is_last = (*p == '\0' ); is_target = (*(p-1 ) == ':' ); if (is_target) { is_first_dep = 1 ; } else if (!is_ignored_file(m, p - m)) { *p = '\0' ; if (is_first_dep) { if (!saw_any_target) { saw_any_target = 1 ; xprintf("source_%s := %s\n\n" , target, m); xprintf("deps_%s := \\\n" , target); } is_first_dep = 0 ; } else { xprintf(" %s \\\n" , m); } buf = read_file(m); parse_config_file(buf); free (buf); } if (is_last) break ; m = p + 1 ; } if (!saw_any_target) { fprintf (stderr , "fixdep: parse error; no targets found\n" ); exit (1 ); } xprintf("\n%s: $(deps_%s)\n\n" , target, target); xprintf("$(deps_%s):\n" , target); } int main (int argc, char *argv[]) { const char *depfile, *target, *cmdline; void *buf; depfile = argv[1 ]; target = argv[2 ]; cmdline = argv[3 ]; xprintf("cmd_%s := %s\n\n" , target, cmdline); buf = read_file(depfile); parse_dep_file(buf, target); free (buf); return 0 ; }
scripts/Makefile.autoconf 用于生成配置文件
产生软链接 用于包含头文件
1 lrwxrwxrwx 1 embedsky embedsky 12 Jan 5 12:42 arch -> arch-stm32h7/
生成include/config.h文件 用于配置文件
1 2 3 4 5 6 #define CFG_BOARDDIR board/st/stm32h750-art-pi #include <configs/stm32h750-art-pi.h> #include <asm/config.h> #include <linux/kconfig.h> #include <config_fallbacks.h>
生成u-boot.cfg文件 用于提取宏定义
1 2 3 4 5 #define CONFIG_ETH 1 #define CONFIG_CMD_FAT 1 #define CONFIG_TOOLS_SHA1 1 #define CONFIG_HAVE_TEXT_BASE 1 #define CONFIG_BOOTM_NETBSD 1
生成include/autoconf.mk文件 用于U-Boot常规配置
生成include/autoconf.mk.dep文件 用于生成自动配置文件的依赖关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 include/config/auto .conf: include/config.h include/linux/kconfig.h \ include/generated/autoconf.h include/configs/stm32h750-art-pi.h \ include/config.h arch/arm/include/asm /config.h include/linux/kconfig.h \ include/config_fallbacks.h include/linux/sizes.h include/linux/const .h \ include/config_distro_bootcmd.h include/linux/kconfig.h: include/generated/autoconf.h: include/configs/stm32h750-art-pi.h: include/config.h: arch/arm/include/asm /config.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 __all: include/autoconf.mk include/autoconf.mk.dep ifeq ($(shell grep -q '^CONFIG_SPL=y' include/config/auto.conf 2>/dev/null && echo y) ,y)__all: spl/include/autoconf.mk endif ifeq ($(shell grep -q '^CONFIG_TPL=y' include/config/auto.conf 2>/dev/null && echo y) ,y)__all: tpl/include/autoconf.mk endif ifeq ($(shell grep -q '^CONFIG_VPL=y' include/config/auto.conf 2>/dev/null && echo y) ,y)__all: vpl/include/autoconf.mk endif quiet_cmd_autoconf_dep = GEN $@ cmd_autoconf_dep = $(CC) -x c -DDO_DEPS_ONLY -M -MP $(c_flags) \ -MQ include /config/auto.conf include /config.h > $@ || { \ rm $@ ; false; \ } include/autoconf.mk.dep: include/config.h FORCE $(call cmd,autoconf_dep) quiet_cmd_autoconf = GEN $@ cmd_autoconf = \ sed -n -f $(srctree) /tools/scripts/define2mk.sed $< | \ while read line; do \ if [ -n "${KCONFIG_IGNORE_DUPLICATES}" ] || \ ! grep -q "$${line%=*}=" include /config/auto.conf; then \ echo "$$line" ; \ fi; \ done > $@ include/autoconf.mk: u-boot.cfg $(call cmd,autoconf) quiet_cmd_u_boot_cfg = CFG $@ cmd_u_boot_cfg = \ $(CPP) $(c_flags) $2 -DDO_DEPS_ONLY -dM include /config.h > $@ .tmp && { \ grep 'define CONFIG_' $@ .tmp | \ sed '/define CONFIG_IS_ENABLED(/d;/define CONFIG_IF_ENABLED_INT(/d;/define CONFIG_VAL(/d;' > $@ ; \ rm $@ .tmp; \ } || { \ rm $@ .tmp; false; \ } u-boot.cfg: include/config.h FORCE $(call cmd,u_boot_cfg) define filechk_config_h (echo "/* Automatically generated - do not edit */" ; \ echo \ $(if $(CONFIG_SYS_CONFIG_NAME) ,echo \#include \<configs/$(CONFIG_SYS_CONFIG_NAME) .h\> ;) \ echo \ echo \ echo \ endef include/config.h: scripts/Makefile.autoconf create_symlink FORCE $(call filechk,config_h) PHONY += create_symlink create_symlink: ifdef CONFIG_CREATE_ARCH_SYMLINK $(Q) if [ -d arch/$(ARCH) /mach-$(SOC) /include /mach ]; then \ dest=../../mach-$(SOC) /include /mach; \ else \ dest=arch-$(if $(SOC) ,$(SOC) ,$(CPU) ) ; \ fi; \ ln -fsn $$dest arch/$(ARCH) /include /asm/arch endif
1 2 3 4 5 6 7 PHONY += scripts_basic scripts_basic: $(Q) $(MAKE) $(build) =scripts/basic $(Q) rm -f .tmp_quiet_recordmcount
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=scripts/basic
解析
这是一种不指定目标的情况,由于未指定目标,这时会使用Makefile.build中的默认目标(第一个目标)__build。
__build在Makefile.build中的构建规则为:
1 2 3 4 5 __build: $(if $(KBUILD_BUILTIN) ,$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES) ,$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @:
__build的变量解析与溯源 1 2 3 4 5 __build: $(if $(KBUILD_BUILTIN) ,$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES) ,$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @:
语句$(if $ (KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y))
为空。
KBUILD_MODULES
在顶层makefile中为空,$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target))
执行为空
语句$(subdir-ym)
为空
$(KBUILD_BUILTIN) 1 2 3 4 KBUILD_MODULES := KBUILD_BUILTIN := 1 include scripts/Kbuild.include
1 2 3 4 $(if $(KBUILD_BUILTIN) ,$(builtin-target) $(lib-target) $(extra-y)) $(builtin-target) $(lib-target) $(extra-y)
$(lib-target)
因为都是空的,所以lib-target 为空 不会执行
1 2 3 4 5 6 7 lib-y := lib-m := ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),)lib-target := $(obj) /lib.a endif
$(builtin-target)
因为都是空的,所以builtin-target 为空 不会执行
1 2 3 4 5 6 7 8 9 10 obj-y := obj-m := subdir-m := ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)builtin-target := $(obj) /built-in.o endif
$(subdir-ym)
1 2 3 4 5 subdir-ym := $(sort $(subdir-y) $(subdir-m)) ...... subdir-ym := $(addprefix $(obj) /,$(subdir-ym) )
$(subdir-y) $(subdir-m)
为空,subdir-ym
为空
$(always) 重点关注
在scripts/Makefile.build有如下定义
1 2 3 4 5 kbuild-dir := $(if $(filter /%,$(src) ) ,$(src) ,$(srctree) /$(src) ) kbuild-file := $(if $(wildcard $(kbuild-dir ) /Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file)
src的定义
在scripts/Makefile.build有如下定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 prefix := vpl src := $(patsubst $(prefix) /%,%,$(obj) ) ifeq ($(obj) ,$(src) )prefix := tpl src := $(patsubst $(prefix) /%,%,$(obj) ) ifeq ($(obj) ,$(src) )prefix := spl src := $(patsubst $(prefix) /%,%,$(obj) ) ifeq ($(obj) ,$(src) )prefix := . endif endif endif
在命令make -f ./scripts/Makefile.build obj=scripts/basic
我们传入了obj=scripts/basic
所以src = obj = scripts/basic, prefix := .
setlocalversion 用于设置版本号 include/generated/version_autogenerated.h
调用顺序:prepare(基本是所有步骤的前置条件) -> prepare0 -> archprepare -> prepare1 -> $(version_h) -> filechk_version.h ->生成include/generated/version_autogenerated.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 define filechk_version.h (echo \ echo \ echo \ echo \ sed -e "s/^0*//" ); \ echo \ echo \ echo \ endef $(version_h) : include /config/uboot.release FORCE $(call filechk,version.h)
1 2 3 4 5 6 7 #define PLAIN_VERSION "2025.04-rc1" #define U_BOOT_VERSION "U-Boot " PLAIN_VERSION #define U_BOOT_VERSION_NUM 2025 #define U_BOOT_VERSION_NUM_PATCH 4 #define HOST_ARCH 0x00a7 #define CC_VERSION_STRING "arm-none-eabi-gcc (15:9-2019-q4-0ubuntu1) 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599]" #define LD_VERSION_STRING "GNU ld (2.34-4ubuntu1+13ubuntu1) 2.34"
scripts/setlocalversion
makefile中的调用,设置版本号与调用的输入命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 VERSION = 2025 PATCHLEVEL = 04 SUBLEVEL = EXTRAVERSION = -rc1 NAME = UBOOTVERSION = $(VERSION) $(if $(PATCHLEVEL) ,.$(PATCHLEVEL) # SUBLEVEL不为空,在VERSION中添加.SUBLEVEL $(if $(SUBLEVEL) ,.$(SUBLEVEL) ) ) $(EXTRAVERSION) define filechk_uboot.release KERNELVERSION=$(UBOOTVERSION) $(CONFIG_SHELL) $(srctree) /scripts/setlocalversion $(srctree) endef PHONY += checkstack ubootrelease ubootversion ubootrelease: @$(filechk_uboot.release) @echo ' ubootrelease - Output the release version string (use with make -s)'
脚本scripts/setlocalversion
的作用是设置版本号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 collect_files (){ local file res= for file; do case "$file " in *\~*) continue ;; esac if test -e "$file " ; then res="$res$(cat "$file " ) " fi done echo "$res " } scm_version (){ local short=false local no_dirty=false local tag while [ $# -gt 0 ]; do case "$1 " in --short) short=true ;; --no-dirty) no_dirty=true ;; esac shift done cd "$srctree " if test -n "$(git rev-parse --show-cdup 2>/dev/null) " ; then return fi if ! head =$(git rev-parse --verify HEAD 2>/dev/null); then return fi version_tag=v$(echo "${KERNELVERSION} " | sed -E 's/^([0-9]+\.[0-9]+)\.0(.*)$/\1\2/' ) tag=${file_localversion#-} desc= if [ -n "${tag} " ]; then desc=$(git describe --match=$tag 2>/dev/null) fi if [ -z "${desc} " ] && [ -n "${file_localversion} " ]; then tag="${version_tag} ${file_localversion} " desc=$(git describe --match=$tag 2>/dev/null) fi if [ -z "${desc} " ]; then tag="${version_tag} " desc=$(git describe --match=$tag 2>/dev/null) fi if [ "${tag} " != "${desc} " ]; then if $short ; then echo "+" return fi if [ -n "${desc} " ]; then echo "${desc} " | awk -F- '{printf("-%05d", $(NF-1))}' fi printf '%s%s' -g "$(echo $head | cut -c1-12) " fi if ${no_dirty} ; then return fi if { git --no-optional-locks status -uno --porcelain 2>/dev/null || git diff-index --name-only HEAD } | read dummy; then printf '%s' -dirty fi } if test "$1 " = "--no-local" ; then no_local=true shift fi srctree=. if test $# -gt 0; then srctree=$1 shift fi if test $# -gt 0 -o ! -d "$srctree " ; then usage fi file_localversion="$(collect_files localversion*) " if test ! "$srctree " -ef .; then file_localversion="${file_localversion} $(collect_files "$srctree " /localversion*) " fi if ${no_local} ; then echo "${KERNELVERSION} $(scm_version --no-dirty) " exit 0 fi if ! test -e include/config/auto.conf; then echo "Error: kernelrelease not valid - run 'make prepare' to update it" >&2 exit 1 fi config_localversion=$(sed -n 's/^CONFIG_LOCALVERSION=\(.*\)$/\1/p' include/config/auto.conf | tr -d '"' ) if grep -q "^CONFIG_LOCALVERSION_AUTO=y$" include/config/auto.conf; then scm_version="$(scm_version) " elif [ "${LOCALVERSION+set} " != "set" ]; then scm_version="$(scm_version --short) " fi echo "${KERNELVERSION} ${file_localversion} ${config_localversion} ${LOCALVERSION} ${scm_version} "
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 手动输入 $ KERNELVERSION=2025.04-rc1 ./scripts/setlocalversion . 2025.04-rc1-00007-ga2b489b170f8 # menuconfig中设置LOCALVERSION="mytest" $ KERNELVERSION=2025.04-rc1 ./scripts/setlocalversion . 2025.04-rc1mytest-00007-ga2b489b170f8 # git 有变更后执行结果 $ KERNELVERSION=2025.04-rc1 ./scripts/setlocalversion . 2025.04-rc1mytest-00007-ga2b489b170f8-dirty # 加入--no-local参数的结果 $ KERNELVERSION=2025.04-rc1 ./scripts/setlocalversion --no-local . 2025.04-rc1-00007-ga2b489b170f8 # 去掉LOCALVERSION_AUTO=y的结果 $ KERNELVERSION=2025.04-rc1 ./scripts/setlocalversion . 2025.04-rc1+ # 根目录下创建localversion文件,写入myfile $ $ echo "myfile" > localversion;KERNELVERSION=2025.04-rc1 ./scripts/setloc alversion . 2025.04-rc1myfile-00007-ga2b489b170f8