makefile 基础

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/ ./”。
    大神
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值