makefile各种$
前言
一、各种$
小菜
> $@ 表示目标文件
> $^ 表示所有的依赖文件
> $< 表示第一个依赖文件
> $? 表示比目标还要新的依赖文件列表
> $%:仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是“foo.a(bar.o)”,那么,
“$%”就是“bar.o”,“$@”就是“foo.a”。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),
那么,其值为空。
> $+ 这个变量很像“$^”,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
> $* 这个变量表示目标模式中“%”及其之前的部分。如果目标是“dir/a.foo.b”,并且目标的模式是“a.%.b”,
那么,“$*”的值就是“dir/a.foo”。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的
定 义,那么“$*”也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么“$*”就是除了后缀
的那一部分。例如:如果目标是“foo.c”,因为“.c”是make所能识别的后缀名,所以,“$*”的值就是“foo”。
这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用“$*”,除非是在
隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么“$*”就是空值。
二、 自动处理头文件的依赖关系
现在我们的Makefile写成这样:
all: main
main: main.o stack.o maze.o
gcc $^ -o $@
main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h
clean:
-rm main *.o
.PHONY: clean
按照惯例,用all做缺省目标。现在还有一点比较麻烦,在写main.o、stack.o和maze.o这三个目标的规则时要查看源代码,找出它们依赖于哪些头文件,这很容易出错,一是因为有的头文件包含在另一个头文件中,在写规则时很容易遗漏,二是如果以后修改源代码改变了依赖关系,很可能忘记修改Makefile的规则。为了解决这个问题,可以用gcc的-M选项自动生成目标文件和源文件的依赖关系:
gcc -M main.c
main.o: main.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \
/usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \
/usr/lib/gcc/i486-linux-gnu/4.3.2/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/typesizes.h \
/usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i486-linux-gnu/4.3.2/include/stdarg.h \
/usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h main.h \
stack.h maze.h
-M选项把stdio.h以及它所包含的系统头文件也找出来了,如果我们不需要输出系统头文件的依赖关系,可以用-MM选项:
all: main
main: main.o stack.o maze.o
gcc $^ -o $@
clean:
-rm main *.o
.PHONY: clean
sources = main.c stack.c maze.c
include $(sources:.c=.d)
%.d: %.c
set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
sources变量包含我们要编译的所有.c文件,$(sources:.c=.d)是一个变量替换语法,把sources变量中每一项的.c替换成.d,所以include这一句相当于:
include main.d stack.d maze.d
类似于C语言的#include指示,这里的include表示包含三个文件main.d、stack.d和maze.d,这三个文件也应该符合Makefile的语法。如果现在你的工作目录是干净的,只有.c文件、.h文件和Makefile,运行make的结果是:
$ make
Makefile:13: main.d: No such file or directory
Makefile:13: stack.d: No such file or directory
Makefile:13: maze.d: No such file or directory
set -e; rm -f maze.d; \
cc -MM maze.c > maze.d.$$; \
sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \
rm -f maze.d.$$
set -e; rm -f stack.d; \
cc -MM stack.c > stack.d.$$; \
sed 's,\(stack\)\.o[ :]*,\1.o stack.d : ,g' < stack.d.$$ > stack.d; \
rm -f stack.d.$$
set -e; rm -f main.d; \
cc -MM main.c > main.d.$$; \
sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.$$ > main.d; \
rm -f main.d.$$
cc -c -o main.o main.c
cc -c -o stack.o stack.c
cc -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main
一开始找不到.d文件,所以make会报警告。但是make会把include的文件名也当作目标来尝试更新,而这些目标适用模式规则%.d:
%c,所以执行它的命令列表,比如生成maze.d的命令:
set -e; rm -f maze.d; \
cc -MM maze.c > maze.d.$$; \
sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \
rm -f maze.d.$$
sed理解
1:将($@.$$$$)的临时文件中的字符串信息(main.o:main.c defs.h)通过 “<” 输送到sed命令中.
2:sed中的s符号告诉sed命令,这次要做一个替换的任务。s符号的格式为:
[address[,address]] s/pattern-to-find/replacement-pattern/[g p w n]。 下面来匹配上面的示例:
[address[,address]]:是指要处理的行的范围,在这次的操作中采用的是默认值。
pattern-to-find等价于\($*\)\.o[ :]*
replacement-pattern等价于\1.o $@ :
3:Makefile使用%=main进行替换后,命令变成了
sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.pid > main.d ;
接下来就比较好分析了,主要是正则表达式的知识了。
pattern-to-find使用到了4个正则表示式的知识点。
first, \(main\)为创建一个字符标签,给后边的replacement-pattern使用。如\1.o,展开后就是main.o
second, \. 在正则表达式中‘.’作用是匹配一个字符。所以需要使用转义元字符‘\’来转义。
third, [ :] 匹配一组字符里的任意字符 。
forth, *匹配0个或多个前一字符
4 : 通过sed的正则表达式,输入的main.o:main.c defs.h被替换成了main.o main.d : main.c defs.h。
这里还有个有趣的东西,平时我们对命令s符号使用‘/’作为参数分割符,其实‘/’只是一种默认的习惯罢了。
你也可以使用','来作为分割符号,只要前后统一就OK。这里就是使用了','来作为分割符。
注意,虽然在Makefile中这个命令写了四行,但其实是一条命令,make只创建一个Shell进程执行这条命令,这条命令分为5个子命令,用;号隔开,并且为了美观,用续行符\拆成四行来写。执行步骤为:
-
set -e命令设置当前Shell进程为这样的状态:如果它执行的任何一条命令的退出状态非零则立刻终止,不再执行后续命令。
-
把原来的maze.d删掉。
-
重新生成maze.c的依赖关系,保存成文件maze.d.1234(假设当前Shell进程的id是1234)。注意,在Makefile中 有 特 殊 含 义 , 如 果 要 表 示 它 的 字 面 意 思 则 需 要 写 两 个 有特殊含义,如果要表示它的字面意思则需要写两个 有特殊含义,如果要表示它的字面意思则需要写两个,所以Makefile中的四个 传 给 S h e l l 变 成 两 个 传给Shell变成两个 传给Shell变成两个,两个$在Shell中表示当前进程的id,一般用它给临时文件起名,以保证文件名唯一。
-
这个sed命令比较复杂,就不细讲了,主要作用是查找替换。maze.d.1234的内容应该是maze.o: maze.c maze.h
main.h,经过sed处理之后存为maze.d,其内容是maze.o maze.d: maze.c maze.h main.h。 -
最后把临时文件maze.d.1234删掉。
不管是Makefile本身还是被它包含的文件,只要有一个文件在make过程中被更新了,make就会重新读取整个Makefile以及被它包含的所有文件,现在main.d、stack.d和maze.d都生成了,就可以正常包含进来了(假如这时还没有生成,make就要报错而不是报警告了),相当于在Makefile中添了三条规则:
main.o main.d: main.c main.h stack.h maze.h
maze.o maze.d: maze.c maze.h main.h
stack.o stack.d: stack.c stack.h main.h
如果我在main.c中加了一行#include “foo.h”,那么:
1、main.c的修改日期变了,根据规则main.o main.d: main.c main.h stack.h maze.h要重新生成main.o和main.d。生成main.o的规则有两条:
> main.o: main.c main.h stack.h maze.h %.o: %.c
> # commands to execute (built-in):
> $(COMPILE.c) $(OUTPUT_OPTION) $<
第一条是把规则main.o main.d: main.c main.h stack.h maze.h拆开写得到的,第二条是隐含规则,因此执行cc命令重新编译main.o。生成main.d的规则也有两条:
main.d: main.c main.h stack.h maze.h
%.d: %.c
set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
因此main.d的内容被更新为main.o main.d: main.c main.h stack.h maze.h foo.h。
2、由于main.d被Makefile包含,main.d被更新又导致make重新读取整个Makefile,把新的main.d包含进来,于是新的依赖关系生效了。
三、Makefile中的wildcard用法
在Makefile规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:$(wildcard PATTERN…) 。在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。需要注意的是:这种情况下规则中通配符的展开和上一小节匹配通配符的区别。
一般我们可以使用“ ( w i l d c a r d ∗ . c ) ” 来 获 取 工 作 目 录 下 的 所 有 的 . c 文 件 列 表 。 复 杂 一 些 用 法 ; 可 以 使 用 “ (wildcard *.c)”来获取工作目录下的所有的.c文件列表。复杂一些用法;可以使用“ (wildcard∗.c)”来获取工作目录下的所有的.c文件列表。复杂一些用法;可以使用“(patsubst %.c,%.o,$(wildcard *.c))”,首先使用“wildcard”函数获取工作目录下的.c文件列表;之后将列表中所有文件名的后缀.c替换为.o。这样我们就可以得到在当前目录可生成的.o文件列表。因此在一个目录下可以使用如下内容的Makefile来将工作目录下的所有的.c文件进行编译并最后连接成为一个可执行文件:
#sample Makefile
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
这里我们使用了make的隐含规则来编译.c的源文件。对变量的赋值也用到了一个特殊的符号(:=)。
1、wildcard : 扩展通配符
2、notdir : 去除路径
3、patsubst :替换通配符
四、遇到的小问题记录
- set -e: It is used to exit immediately if a command exits with a non-zero status.
ex : 内核编译的过程中
make -f ./scripts/Makefile.build obj=scripts/basic
set -e;
Stop a script immediately.
To stop a script immediately, execute the following command:
set -e
sed -e 命令
-e 脚本命令 该选项会将其后跟的脚本命令添加到已有的命令中。
一般用于多个命令写成一块
ex:
uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/
输出x86 - xz 是一种压缩命令
-c, --stdout, --to-stdout
z, --compress
-d, --decompress, --uncompress
ex :xz -cd linux-4.X.tar.xz | tar xvf -
这里最后的 -,代表的是前面的标准输出,会把标准输出当作标准输入 - makefile 中的 dir 和 shell 中好像不一样呢 shell : dir ~/work/ shuoshi makefile
: $(dir NAMES…) 和 notdir 相反 函数名称:取目录函数—dir。
函数功能:从文件名序列“NAMES…”中取出各个文件名目录部分。文件名的目录部分就是包
含在文件名中的最后一个斜线(“/”)(包括斜线)之前的部分。 $(dir src/foo.c hacks) 返回值为“src/ ./”。
大神