如何编写Makefile(2)-书写规则

目录

一、规则示例

二、规则的语法

三、通配符使用

四、文件搜索

五、伪目标

六、多目标

七、静态模式

八、自动生成依赖性


        我们继续Makefile编写的内容学习~第一节可参考:↓传送门↓

如何编写Makefile(1)-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/L_peanut/article/details/144134136?spm=1001.2014.3001.5501        本节内容主要介绍Makefile的编写规则。

一、规则示例

a.o:a.c a.h
    cc -c -g a.c

        这个示例中,a.o是我们的目标,a.c和a.h是目标a.o所依赖的源文件,只有一个指令cc -c -g a.c(以Tab键开头)。规则是根本意义是:

        (1)文件的依赖关系。a.o依赖于a.c和a.h,如果a.c和a.h的文件日期比a.o文件日期要新,或者a.o不存在,那么依赖关系发生;

        (2)生成方法。cc命令就是生成或更新a.o的命令,其说明了如何生成a.o这个文件。

二、规则的语法

targets:prerequisites
    command
    ...

        或

targets:prerequistes;command
    command
    ...

        targets是文件名,以空格分开,可以使用通配符。一般情况下,目标基本上是一个文件,但也可能是多个文件。

        command是命令行,如果其不与“target:prerequisites”一行,则必须以Tab键开头,如果和prerequisites在一行,可以用分号隔开。

        prerequisites是目标所依赖的文件,如果其中的某个文件比目标文件要新,目标就被认为是“过时的”,被认为是需要重新生成的。

        如果命令过长,可以使用反斜杠(\)作为换行符。make对一行中有多少个字符没有限制。一般来说,make会以UNIX的标准shell,也就是/bin/sh来执行命令。

三、通配符使用

        如果我们需要定义一系列比较类似的文件,则可以使用通配符的形式来高效实现。

        make支持三个通配符:*~

        波浪号(~)字符在文件名中也有比较特殊的用途,如果是~/test,这就表示当前用户的$HOME目录下的test目录,而~user/test则表示用户user的宿主目录下的test目录,在Windows系统中用户没有宿主目录,波浪号所指的目录根据环境变量“HOME”来决定。

        通配符代表一系列文件,如*.c表示所有后缀为c的文件。如果文件名中有通配符,如:*,可以使用转义字符\,如\*来表示真实的*字符。

clean:
    rm -f *.o

        也可以在clean后面加上其它指令,如希望在编译完成之后看看main.c的代码,可以:

clean:
    cat main.c
    rm -f *.o

        通配符也可以在我们规则当中,目标print依赖于所有的.c文件,其中的$?是一个自动化变量。        

print:*.c
    lpr -p $?
    touch print

        通配符可以用在变量中,下面的例子并不代表*.o会展开,objects的值就是*.o。Makefile中的变量类似于C/C++中的宏,如果想要让通配符在变量中展开,也就是让objects的值是所有.o的文件名的集合,可以这样:

objects:=$(wildcard *.o)

        下面是变量使用通配符的一些例子:

1、objects:=$(wildcard *.c)    #列出确定文件夹中的所有.c文件
2、$(patsubst %.c,%.o,$(wildcard *.c))    #列出1中所有文件对应的.o文件
3、objects:=$(patsubst %.c,%.o,$(wildcard *.c))    #由1、2两步,可以编译并链接所有.c和.o文件
   a:$(objects)
       cc -o a $(objects)

四、文件搜索

        如果工程文件很大,会有大量的源文件,我们通常是把这些源文件进行分类,并存放在不同的目录中。所以当make需要去寻找文件的依赖关系时,可以在文件前加上路径,但更好的方法是告诉make一个路径,让其去自动寻找。

        Makefile文件中的特殊变量VPATH就是完成这个功能的,如果没有指明这个变量,make只会在当前目录中去寻找依赖文件和目标文件。如果定义了这个变量,那make就会在当前目录找不到的情况下到指定的目录中寻找文件。

VPATH=src:../headers

        make会按照上面定义指定的两个目录“src”和“../headers”的顺序进行搜索,目录用“冒号”隔开。

        另一个设置文件搜索路径的方法是使用make的“vpath”关键字(全小写),这不是变量,而是一个make关键字,这个VPATH很类似,但是更加灵活。它可以指定不同的文件在不同的搜索目录中。其使用方法有:

vpath <pattern> <directories>
    #为符合模式<pattern>的文件指定搜索目录<directories>
vpath <pattern>
    #清除符合模式<pattern>的文件的搜索目录
vaptah
    #清除所有已被设置好的文件搜索目录

        vpath使用方法中的<pattern>需要包含%字符。%的意思是匹配零或若干字符,(如果要使用%,通过\来转义)。例如,%.h表示所有以.h结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了<pattern>的文件集的搜索的命令。例如:

vpath %.h ../headers

        上面的命令要求make在"../headers"目录下搜索所有以.h结尾的文件。

        我们可以连续使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的<pattern>,或者是被重复了的<pattern>,那make会按照vpath语句的先后顺序来执行搜索。如:

vpath %.c a
vpath %   b
vpath %.c d

上面命令表示.c结尾的文件,先在“a”目录,然后是“b”目录,最后是“d”目录。

vpath %.c a:d
vpath %   b

 上面的语句表示.c结尾的文件,先在“a”目录,然后是“d”目录,最后是“b”目录。

五、伪目标

        第一节中我们就提到过一个“clean”目标,这是一个伪目标。

clean:
    rm *.o temp

        我们并不生成“clean”这个文件,“伪目标”不是一个文件,只是一个label(标签)。由于伪目标不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有显式地指明这个“目标”才能让其生效。当然,伪目标的取名不能和文件名重名,不然就失去伪目标的意义了。所以,为了避免和文件名重名,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是伪目标,向make说明,不管是否有这个文件,这个目标就是伪目标。

.PHONY:clean

        只要有这个声明,不管是否有“clean”这个文件,要运行“clean”这个目标,只有“make clean”的方式。于是可以写成这样:

.PHONY:clean
clean:
    rm *.o temp

        伪目标一般没有依赖文件,但是也可以给伪目标指定依赖文件。伪目标也可以作为“默认目标”,只要将其放在第一个。如果你想要Makefile一次性生成多个可执行文件,通过一个make命令即可实现,所有的目标文件都写在一个Makefile中,则可以利用“伪目标”的特性:

all:test1 test2 test3
.PHONY:all

test1:test1.o other.o
    cc -o test1 test1.o other.o

test2:test2.o
    cc -o test2 test2.o

test3:test3.o other.o
    cc -o test3 test3.o other.o

        上面示例中声明了一个“all”的伪目标,其依赖其它三个目标,同时“all”将被作为make的默认目标。由于默认目标的特性是总会被执行,但“伪目标”只是一个标签不会生成文件,所以不会产生“all”的可执行文件。.PHONY:all声明了“all”这个目标为“伪目标”。这个例子也说明,目标也可以成为依赖,所以伪目标也可以成为依赖,如下所示。

.PHONY:cleanall cleanobj cleandiff

cleanall:cleanobj cleandiff
    rm program

cleanobj:
    rm *.o

cleandiff:
    rm *.diff

        "make cleanall"清除所有文件,“cleanobj”和“cleandiff”这两个伪目标有点类似于子程序,我们可以在命令行输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来实现清理不同类型的文件。

六、多目标

        Makefile支持多目标,且很可能多个目标同时依赖一个文件,并且生成的命令大体类似,我们可以将其合并。多个目标的生成规则的执行命令也可以不是同一个,我们可以使用自动化变量$@来实现,这个变量表示目前规则中所有的目标的集合。如下例子所示。

fastwrite slowwrite:test
    generate test -$(subst write,,$@) > $@

上面语句等价于

fastwrite:test
    generate test -fast > fastwrite
slowwrite:test
    generate test -little > littlewrite

        其中,-$(subst write,,$@)中的$表示执行一个Makefile的函数,函数名为subst,后面的为参数,作用是替换字符串,$@表示目标的集合,就像一个数组,$@依次取出目标,并执行命令。

七、静态模式

        Makefile的静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加灵活。语法如下:

<targets ...>:<target-pattern>:<prereq-patterns ...>
    <commands>
    ...

        targets定义了一系列的目标文件,可以有通配符,是目标的一个集合。

        target-pattern指明了targets的模式,也就是目标集模式。

        prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义。

        比如说,<target-pattern>定义为%.o,意思是<target>;集合中都是以.o结尾的,如果<prereq-patterns>定义为%.c,意思是对<target-pattern>所形成的目标集进行二次定义,计算方法是:取<target-pattern>模式中的%(去掉.o),为其加上.c作为结尾,形成新的集合。

objects=a.o b.o

all:$(objects)

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

        上面的例子指明了我们的目标从$objects中获取,%.o表明要所有以.o结尾的目标,而依赖模式%.c则取%.o的%,也就是a b,并为其加上.c的后缀,即我们的依赖目标是a.c b.c。命令中的$< 和$@是自动化变量,$<表示第一个依赖文件,$@表示目标集(即a.o b.o)。于是将这些规则展开后等价于:

a.o:a.c
    $(CC) -c $(CFLAGS) a.c -o a.o
b.o:b.c
    $(CC) -c $(CFLAGS) b.c -o b.o

        如果我们有几百个%.o文件,只要使用这种“静态模式规则”就可以写完一堆规则,极大提升效率。

files=a.elc b.o c.o

$(filter %.o,$(files)):%.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)):%.elc: %.el
    emacs -f batch-byte-compile $<

        $(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files”集,只要其中模式为%.o的内容。

八、自动生成依赖性

        Makefile中,依赖关系可能需要包含许多头文件,例如main.c中有一句#include "circle.h",那我们的依赖关系为

main.o:main.c circle.h

        对于大型工程,需要了解哪些C文件包含哪些头文件,并且在加入或删除头文件时,都需要修改Makefile。为了避免这种繁杂的事务,我们可以使用C/C++编译的一个功能,即编译器的“-M”选项,添加该参数后可以自动寻找源文件中包含的头文件,并生成一个依赖关系。例如:

cc -M main.c

该语句输出为:

main.o:main.c circle.h

        我们可以写出.c文件和.d文件的依赖关系,并且让make自动更新或生成.d文件,并将其包含在我们的主Makefile文件中,这样就可以自动化生成每个文件的依赖关系了。

%.d:%.c
    @set -e; rm -f $@; \
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$

        这个规则的意思是,所有.d文件依赖于.c文件,rm -f $@表示删除所有的目标,也就是.d文件;第二行的意思是为每个依赖文件$<,也就是.c文件生成依赖文件,$@表示模式%.d文件,如果有一个C文件是name.c,那么%就是name,$$$$是一个随机编号,生成的文件可能是"name.d.12345";第三行使用sed命令做了一个替换;第四行是删除临时文件。

        总之,这个模式要做的事就是在编译器生成的依赖关系中加入.d文件的依赖,把依赖关系:

main.o:main.c circle.h

转换为

main.o main.d:main.c circle.h

        这样.d文件也会自动更新,并自动生成。我们还可以在.d文件中加入不只依赖关系,包括生成的命令也可以一并加入,让每个.d文件都包含一个完整的规则。我们可以使用Makefile的“include”命令,来引入其它Makefile文件:

sources=a.c b.c

include $(sources:.c=.d)

        上面语句中的$(sources:.c=.d)中的.c=.d的意思是做一个替换,把变量$(sources)所有.c的字符串替换成.d。需要注意的是,因为include是按照次序载入文件的,最先载入的.d文件中的目标会成为默认目标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿核试Bug愁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值