Author:Onceday date:2022年8月28日
本文收集整理于陈皓大佬的文章,以便贴合自己的思维逻辑,以下是原文文档:
基础知识请参考前两篇文章:
makefile文件里面,一般具有大量自定义的变量,如何只是一个文件的话,那还好,可以通过编辑器高亮提示和全文查找来捋清楚关系。
但是,如果是Linux内核级别的源码,makefile文件嵌套多,难以找到所有makefile文件,这个时候,很难去确定某一个变量的值是多少,来自命令行输入,还是自动变量,或者是预定义的。
ARCH ?= $(SUBARCH)
# Architecture as present in compile.h
UTS_MACHINE := $(ARCH)
SRCARCH := $(ARCH)
比如上面一段是linux内核makefile的脚本,这里将变量的值又赋给其他变量,如此嵌套下去,很难快速在某个小组件的makefile里确定变量的值。总不能一开始就把全部makefile读一遍吧,这不合适。
因此,可以写一些额外的模式规则,即目标里面带有模式匹配符%,如%.0,表示所有带有后缀.0的目标,如x.o、ss.o等。
例如:
%.o :%.c
gcc -c $< -o $@
当我们使用make this.o时,便会匹配上该规则,相当于:
this.o :this.o
gcc -c this.c -o this.o
$@表示规则中的目标文件集合(即%.o),$<表示依赖目标文件的集合(即%.c)。
当然,实际上不会这么用,因为配合静态模式和自定义变量,可以更加有效率的完成此项工作。
但在此文中,我们输出变量的值,就需要用到这个,一般这些脚本放在了.mk文件里面。
首先感兴趣的可能是系统预定义的变脸和规则,可使用以下命令查看:
make -p -f /dev/null
-p就是输出make所有数据信息,-f是指定makefile文件,/dev/null是一个特殊的设备文件,即空文件。
所以上面命令能显示所有的预定义信息。那么如果想输出实际makefile的信息,可以用以下命令:
make -qp [目标]
-q表示检查所指定的目标需不需要更新,因此不会真的执行命令,类似的命令还有其他的,如
"-n"
"--just-print"
"--dry-run"
"--recon"
上面的四个命令是输出运行过程要执行的规则, 但不会输出变量信息和规则信息。
因此,这些工具要灵活使用,而对于变量信息的输出,最大的问题在于文本太多了。一次性输出这么多信息,并非是我们所希望的。
这里借助脚本的echo指令,可以写出以下规则:
%:
@echo '$*=$($*)'
这个@,其作用是取消在终端输出@echo '$*=$($*)'这个命令的文本。
$:是自动化变量,取目标模式中"%"及其之前的部分,在这里,就是命令行输入的“目标”变量名。$($*)是取“目标”变量名的值。
默认情况下,是可以看到make执行了哪些命令的。但是echo本身会输出替换了变量之后的字符串,因此就不需要重复输出了。如:
test=1245
%:
@echo "$*=$($*)"
那么在命令行里有:
onceday@ubuntu:~$ make untest
untest=
onceday@ubuntu:~$ make test
test=1245
%会匹配所有的目标,如untest和test,但是这个不能输出目标的信息,例如:
first:*.py
@echo "hello world"
test=1245
%:
@echo "$*=$($*)"
second:*.py
@echo "hello"
在这个里面,有:
onceday@ubuntu:~$ make second
hello
onceday@ubuntu:~$ make se
se=
onceday@ubuntu:~$ make
hello world
onceday@ubuntu:~$ make first
hello world
可以看到,如果匹配上其他实际目标,则并不会执行这个模式匹配%:。
因此,可以使用以下这种命令来查看具体执行的目标所包含的命令:
make -n 目标
但是工作不一定会正常,毕竟下一步的命令可能依赖上一步的具体执行结果。
编写模式规则如下:
d-%:
@echo '$*=$($*)'
@echo ' 变量类型= $(origin $*)'
@echo ' 原始值 = $(value $*)'
@echo ' 特点 = $(flavor $*)'
这里,$(origin 函数表示获取变量类型,有如下几种:
“undefined”,如果从来没有定义过。
“default”,如果是一个默认的定义。
“environment”,如果是一个环境变量。
“file”,如果这个变量被定义在Makefile中。
“command line”,如果这个变量是被命令行定义的。
“override”,如果是被override指示符重新定义的。
“automatic”,如果是一个命令运行中的自动化变量。
$(value 函数返回变量未被展开的样子。
$(flavor 函数表示该变量量是如何展开的,有两个值,simple表示是一般展开的变量,recursive表示递归展开的变量。
如下是一个测试的makefile文件:
G := gcc
foo = $(goo)
goo = $(hoo)
hoo = joo?
CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar
CFLAGS := $(CFLAGS) -Wall
test = $(CFLAGS) -xxx
注意这里:=赋值符号表示不使用“后定义的变量”。
在命令行进行测试:
onceday@ubuntu:~$ make d-G
G=gcc
变量类型= file
原始值 = gcc
特点 = simple
onceday@ubuntu:~$ make d-foo
foo=joo?
变量类型= file
原始值 = $(goo)
特点 = recursive
onceday@ubuntu:~$ make d-CFLAGS
CFLAGS=-Ifoo -Ibar -O -Wall
变量类型= file
原始值 = -Ifoo -Ibar -O -Wall
特点 = simple
onceday@ubuntu:~$ make d-test
test=-Ifoo -Ibar -O -Wall -xxx
变量类型= file
原始值 = $(CFLAGS) -xxx
特点 = recursive
注意,这里echo ''必须使用单引号,如果是双引号,那么shell也会对$(xxx)进行取值,然后就是报错和显示异常。
实际测试输出的结果非常明确,递归展开的变量其原始值都包含$(xx),直接展开的,其原始值都是固定的,无需根据后面的运行结果确定。
这里就凸显的:=的作用,拒绝使用后定义的变量,避免在嵌套makefile中,前面的变量被后面使用的变量给干扰了。
上面的模式规则和makefile写在了一个文件里,因此命令行可以直接填目标即可。实际情况下,往往是要同时指定makefile和mk(模式匹配)文件。
即:
make -f makefile -f var.mk d-xxxx
这样就将模式规则和具体的makefile文件分开了。