[003-Makefile-笔记] Makefile的规则

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pengfei240/article/details/53504752

终极目标

  1. makefile 文件中第一个规则的目标被称为“终极目标”,有两种情况例外:
    • 目标名以点号“.”开始的,并且其后不存在斜线“/”
    • 模式规则的目标
  2. 规则的顺序在 makefile 文件中没有意义
  3. 如果 makefile 的第一个规则有多个目标的话,那么多个目标中的第一个将会被作为 makefile 的“终极目标”

规则语法

语法格式

TARGETS : PREREQUISITES
    COMMAND
    ...

或者

TARGETS : PREREQUISITES ; COMMAND
    COMMAND
    ...

语法要点

  1. 当作为独立的命令行时,必须以[Tab]字符开始
  2. 符号“$”有特殊的含义(表示变量或者函数的引用),在规则中使用该符号的地方,需要写成“$$
  3. 对于较长的行,可以使用反斜线“\”将其书写到几个独立的物理行上

规则的中心思想

目标文件的内容是由依赖文件决定,依赖文件的任何一处改动,将导致目前已经存在的目标文件过期。

依赖的类型

normal prerequisites

规则中如果依赖文件的任何一个比目标文件新,则认为规则的目标已经过期,而需要重建目标文件。

order-only prerequisites

当目标文件存在时,此依赖不会参与规则的执行过程。

规则格式如下:

TARGETS : NORMAL-PREREQUISITES | ORDER-ONLY-PREREQUISITES
  • 对于 NORMAL-PREREQUISITES 的处理与 normal prerequisites 一致
  • 对于ORDER-ONLY-PREREQUISITES 来说,只有在目标文件不存在的情况下,才会参与规则的执行。当目标文件存在时,此依赖不会参与规则的执行

示例:

all: target

target: dep1 | dep2
        @echo make all
        touch target

dep1:
        @echo make dep1
        touch dep1

dep2:
        @echo make dep2
        touch dep2

运行结果:

$ make all
make dep1
touch dep1
make dep2
touch dep2
make all
touch target
$ touch dep1
$ make all
make all
touch target
$ touch dep2
$ make all
make: Nothing to be done for 'all'.
  1. 第一次执行make all,生成了依赖文件dep1和dep2
  2. 修改dep1后执行make all,重新生成了target(依赖文件发生变化)
  3. 修改dep2后执行make all,target没有重新生成(dep2是order-only prerequisites模式)

实际运用中,可以在编译前生成一些目录(比如存放中间过程和最终结果的out目录):

outdir:=out

all: application

application: | ${outdir}

${outdir}:
        @echo mkdir -p $@
        @mkdir -p $@

application:
        @echo make ${outdir}/application
        touch ${outdir}/application

运行结果:

$ make all
mkdir -p out
make out/application
touch out/application
$ touch out/dumy
$ make all
make out/application
touch out/application
  1. 第一次执行make all,生成了目录out
  2. 更新目录out下的文件后执行make all,此时不会重新创建目录

文件名使用通配符

  1. 可使用的通配符有:“*”、“?”和“[…]”
  2. 在 makefile 中通配符的用法和含义和 Bourne shell 完全相同
  3. 可以在规则的目标、依赖中,在读取 Makefile 时会自动对其进行匹配处理
  4. 可以在规则的命令中,通配符的通配处理是在 shell 执行此命令时完成的
  5. 其它场景下需要通过函数 wildcard 来使用通配符,比如查找所有.c文件:
source := $(wildcard *.c)

目录搜寻

目的

当工程的目录结构发生变化后,不需要更改 Makefile 的规则,只更改依赖文件的搜索目录。

一般搜索

通过变量“VPATH”指定文件的搜索路径(包括目标文件和依赖文件),当规则的依赖文件在当前目录不存在时,makefile 会在此变量所指定的目录中寻找这些依赖文件。

示例:

VPATH = ../include:/usr/include:/usr/local/include

选择性搜索

使用关键字“vpath”为不同类型的文件指定不同的搜索路径。有三种使用方式:

  1. vpath PATTERN DIRECTORIES
    为所有符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES”。多个目
    录使用空格或者冒号分开
  2. vpath PATTERN
    清除之前为符合模式“PATTERN”的文件设置的搜索路径
  3. vpath
    清除所有已被设置的文件搜索路径

示例:
所有 *.mk 的文件在 ../header 目录下寻找

vpath %.mk ../header 

注意:
1. 当前目录永远是第一优先级查找
2. 路径仅限于在 makefile 文件内容中出现的文件。 并不能指定源文件中包含的头文件所在的路径
3. 在 makefile 中如果存在连续的多个 vpath 语句使用了相同的“PATTERN”,搜索某种模式文件的目录将是所有的通过vpath 指定的符合此模式的多个目录,其搜索目录的顺序由 vpath 语句在 makefile 中出现的先后次序来决定

目录搜索的机制

  1. 如果规则的目标文件在 makefile 文件所在的目录下不存在,那么就执行目录搜寻
  2. 如果目录搜寻成功,那么搜索到的完整的路径名就被作为临时的目标文件保存
  3. 对于规则中的所有依赖文件使用相同的方法处理
  4. 完成第三步的依赖处理后,makefile就可以决定规则的目标是否需要重建:
    1. 规则的目标不需要重建:通过目录搜索得到的所有完整的依赖文件路径名有效,同样,规则的目标文件的完整的路径名同样有效。已经存在的目标文件所在的目录不会被改变
    2. 规则的目标需要重建:通过目录搜索所得到的目标文件的完整的路径名无效,规则中的目标文件将会在工作目录下重建。此时依赖文件的完整路径名依然有效
  5. 可以使用“GPATH”变量来指定目标文件所在的目录:
LIBS = libtest.a
GPATH = src
VPATH = src
libtest.a : sum.o memcp.o
    $(AR) $(ARFLAGS) $@ $^

库文件和搜索目录

makefile 中程序链接的静态库、共享库同样也可以通过搜索目录得到。格式为 -INAME。

当规则中依赖文件列表中存在一个“-lNAME”形式的文件时,其搜索顺序如下:
1. makefile 在执行规则时会在当前目录下搜索名为“libNAME.so”的文件
2. 搜索使用“VPATH”或者“vpath”指定的搜索目录
3. 搜索系统库文件存在的默认目录:“/lib”、“/usr/lib”和“PREFIX/lib”
4. 如果没有找到的话,那么makefile将会从step 1开始寻找“libNAME.a”

示例:

foo : foo.c -lcurses
    cc $^ -o $@

注意:
1. “-lNAME”只是告诉了链接器在生成目标时需要链接某个库文件,并没有告诉 make 程序其依赖的库文件应该如何重建
2. “-INAME”的规则是由变量“.LIBPATTERNS”指定的。默认情况下,“.LIBPATTERNS”的值为:“lib%.so lib%.a”。我们也可以将此变量置空,以取消链接器对“-lNAME”格式的展开

伪目标

它不是一个真正的文件名,在执行 make 时可以指定这个目标来执行其所在规则定义的命令。也可以将伪目标称为标签,格式如下:

.PHONY : target
target:
    command

注意:在书写伪目标规则时,首先需要声明目标是一个伪目标,之后才是伪目标的规则定义。

使用原因

  1. 避免在 makefile 中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突
  2. 声明为伪目标后,make 在执行此规则时不会去试图查找隐含规则来创建它。这样提高了 make 的执行效率

空目标文件

通常用于依赖文件被改变之后,执行所在规则中定义的命令(如果依赖文件没有被改变则不执行命令)。

示例:

print: foo.c bar.c
    lpr -p $?
    touch print    # 更新空文件时间戳,使得命令执行后目标文件比依赖文件新

多目标

一个规则中可以有多个目标,规则所定义的命令对所有的目标有效。一个具有多目标的规则相当于多个规则,往往在命令中会使用自动化变量“$@”。

示例:

bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) > $@

其等价于:

bigoutput : text.g
    generate text.g -big > bigoutput
littleoutput : text.g
    generate text.g -little > littleoutput

多规则目标

  1. 一个文件可以作为多个规则的目标,此时,以这个文件为目标的规则的所有依赖文件将会被合并成一个依赖文件列表(可以理解为所有依赖合成为一个规则)
  2. 对于一个多规则的目标,重建此目标的命令只能出现在一个规则中,如果多个规则同时给出重建此目标的命令,makefile将使用最后一个规则中所定义的命令,同时提示错误信息
  3. 使用“.”开头的多规则目标文件,可以在多个规则中给出多个重建命令。这种方式只是为了兼容性,不建议使用

示例:(摘自u-boot)

all:
sinclude $(obj)include/autoconf.mk.dep
sinclude $(obj)include/autoconf.mk

...

all:
    all=${ALL})$(ALL)

根据多规则的解析规则,实际上执行的是最后一个规则的命令;第一个 all 的用途是防止 .dep 文件中的文件依赖成为终极规则,换句话说,当用户输入 make 时(非make all),会与 make all 一样,将 all 做为终极规则。

静态模式

定义

规则中存在多个目标,并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。它不需要多个目标具有相同的依赖。

语法

TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
    COMMANDS

示例

objects = foo.o bar.o
all: $(objects)
    ...

$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@

相当于

foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o

静态模式和隐含规则的异同

异同点 静态模式 隐含规则
相同点 构建文件依赖关系 构建文件依赖关系
不同点 必须明确指定规则 当没有指定具体的规则时,使用隐含规则
对同一个目标,其规则是唯一的 可以存在多个目标规则,执行的顺序取决于规则定义的顺序

双冒号规则

双冒号规则允许在多个规则中为同一个目标指定不同的重建目标的命令。

注意:
1. 一个目标可以出现在多个规则中,但是这些规则必须是同一类型的规则。都是普通规则或都是双冒号规则
2. 当同一个文件作为多个双冒号规则的目标时,这些不同的规则会被独立的处理,而不是像普通规则那样合并所有的依赖到一个目标文件中

示例:

Readme:: man.txt
        echo "man is the latest document" > Readme

Readme:: info.txt
        echo "info is the latest document" > Readme

执行结果如下:

# 所有文件都更新了,所以两个命令都执行
$ touch man.txt
$ touch info.txt
$ make
echo "man is the latest document" > Readme
echo "info is the latest document" > Readme

# 更新了man.txt,所以使用man的规则生成Readme
$ echo man > man.txt
$ make
echo "man is the latest document" > Readme

# 更新了info.txt,所以使用info的规则生成Readme
$ echo info > info.txt 
$ make
echo "info is the latest document" > Readme

自动产生依赖

使用GCC的“-M”选项来自动找寻源文件中包含的头文件,并生成文件的依赖关系。命令的使用见链接: blog.csdn.net/pengfei240/article/details/53165151

示例:

%.d: %.c
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$
  • 第一行:使用 C 编译器自动生成依赖文件的头文件的依赖关系,并输出成为一个临时文件
  • 第二行:使用 sed 处理第二行已产生的那个临时文件并生成此规则的目标文件,如下:
main.o main.d : main.c defs.h
  • 第三行:删除临时文件

猜你喜欢

转载自blog.csdn.net/pengfei240/article/details/53504752
今日推荐