makefile(二):普通规则

一.普通规则

规则是makefile的主要内容,它决定了编译系统何时,如何来编译指定的目标。规则的语法如下:

目标:依赖   
    指令
请特别注意,指令前面有一个tab空格。

同时指令可以直接写在依赖后面,即同一行上,并且这种情况下指令前可以不用tab键,但各个指令以分号分隔,这种写法常常用来写一个空命令,如下,

target:;

普通命令请不要这样书写,因为这样并不便于阅读

另外,在GNU make手册中提到,我们也可以更改.RECIPEPREFIX变量的值来指定指令前面的字符。例子如下:

.RECIPEPREFIX := >
all:
>@echo wanbiao

不过在我目前使用的GNU make 3.81下进行测试,并不能正确运行,这个可以作为一个扩展功能。(也或许是我自己的书写错误,没有继续深究)

同时依赖也可以当成其他的规则的目标,如下例子:

all:a.o b.o
    gcc –o all a.o b.o 
a.o:a.c
    gcc –c a.c
b.o:b.c
    gcc –c b.c

那么当我们直接执行make命令的时候,它的执行顺序如下:
1.首先扫描到all目标,然后把它当作终极目标,即整个make的执行都是为了建立一个all目标出来,
2.查看当前目录下是否有all文件,如果没有,那么就根据依赖进行生成,此时检测依赖,a.o和b.o,发现在当前目录下没有这两个文件。那么就将这两个依赖作为目标,查找规则。
3.此时查找到a.o的规则,和b.o的规则,发现他们分别依赖a.c和b.c,然后继续检测a.c和b.c,看看当前目录下面有没有,发现有a.c和b.c,
4.那么就开始生成目标a.o和b.o即,执行gcc –c a.c 和 gcc –c b.c命令生成a.o 和b.o此时有了a.o和b.o那么就可以生成终极目标all了,
5.此时执行gcc –o all a.o b.o生成终极目标。整个makefile执行完毕。
对上面的一些补充说明:
make在没有指定目标的时候,makefile遇到的第一个有效目标作为终极目标,因此终极目标应该写在前面,而其他的目标顺序则没有要求。如果一个规则有多个目标,那么将其第一目标作为终极目标。
另外,有效的目标指:1.不是以点号开头的目标(当然如果点号后面是一个路径,那么这个目标也可以作为终极目标);2。也不能是模式目标,关于模式目标后面会详细叙述。
当多个目标出现在同一条规则时,称为:多目标规则。这些规则具有同样的依赖,同样的命令行。相当于多条规则,写在一起了,不推荐此种用法。
当一个目标出现在多条规则中时,称为:多规则目标。这些规则中只能有一条规则有命令行,如果其他规则也有,那么会报错。make在执行时,会将所有规则的依赖合并在一起形成一个依赖列表,最终形成一个规则。

二.静态模式规则

存在如下这种情况,某一类的目标,具有相类似的依赖。此时如果用普通规则,那么需要为每个都这么写,因此,为了减少工作量,引入了静态模式规则,这个和后面的模式规则有一定的区别,后面叙述模式规则。
他的语法规则如下:

目标列表:目标模式:依赖模式
    命令行

目标列表包含了这个规则的全部目标,注意不能包含不适用此条规则的其他目标。
目标模式:是一个包含%的模式字符串。%匹配任意的字符。
依赖模式:也是一个包含%的模式字符串。依赖中的%的具体值等于目标中的%的值。
因为这里用到了模式字符串,因此,在命令行中就不能将一些命令写死,需要用到自动化变量,自动化变量的详细说明,可以参考后面的叙述。
举例如下:

objects := foo.o bar.o
all:$(objects)
$(objects):%.o:%.c
    $(CC) –c $(CFLAGS) $< -o $@

对于foo.o来说,%的值为foo,那么他的依赖中的%的值也为foo,那么依赖为foo.c
如果需要使用%的值可以使用$*,$*在这种模式下代表%的值。
注意:
$(objects)不能包含不匹配%.o:%.c规则的文件,如下。

objects := foo.o bar.o test.fle
all:$(objects)
    $(objects):%.o:%.c
    $(CC) –c $(CFLAGS) $< -o $@

这个会得到一个错误的提示。改进版如下:

objects := foo.o bar.o test.fle
all:$(objects)
    $(filter %.o,$(objects)):%.o:%.c
    $(CC) –c $(CFLAGS) $< -o $@

这里使用了filter函数,他的作用是:从$(objects)过滤出.o结尾的文件。

三.双冒号规则

双冒号规则,就是用::代替:的规则。这种规则和普通的规则的处理不太一样。当同一个目标出现在多个规则中时,普通规则只允许一条规则有命令行,而双冒号规则允许所有的规则都有命令行。make不会将这些规则进行合并,当不同的双冒号规则需要更新的时候,可以执行不同的命令,举例如下:

ob::foo.c
    $(CC) $(CFLAGS) $^ -o $@
ob::bar.c
    $(CC) $(CFLAGS) -g $^ -o $@

当foo.c比ob新时,则执行$(CC) $(CFLAGS) $^ -o $@命令,当bar.c比ob新时,执行$(CC) $(CFLAGS) -g $^ -o $@命令
那么当这两条规则为普通规则的时候,make将会提示错误信息

优势:
双冒号规则给我们提供了:根据不同的依赖来执行不同命令的机制。

四:模式规则

考虑上面的静态模式规则,如果我们能将目标列表增加到,所有makefile文件里面定义的目标,那么写出来的目标列表会变得非常冗余。此时可以去掉这个目标列表只剩下,目标模式和依赖模式。如下:

目标模式字符串:依赖模式字符串
    命令行

这种规则称为:模式规则
其使用跟静态模式规则一模一样,并且它没有了目标列表这部分,也就没有了目标列表不能包含不符合此规则的限制。
模式规则也可以用来覆盖makefile的隐含规则,详细细节可参考隐含规则一节。

五.自动化变量

当时用了静态模式规则,和模式规则的时候,如果命令行不能依据目标的%进行相应的变化,那么模式规则和静态模式规则毫无意义。因此这里需要使用自动化变量。
常见自动化变量的说明如下:
$@ 代表目标的依赖文件。
$% 当规则的目标是一个静态库文件的时候,代表静态库的一个成员
$< 代表第一个依赖文件
$? 所有比目标更新的依赖文件
$^ 代表所有依赖文件,当依赖文件重复时,去掉重复文件
$+ 与$^相同,但是保留重复的依赖文件
$* 在模式规则和静态模式规则中,代表%的值。
在老版本的makefile中,还可以通过组合D或者F来,表示目录和文件名,如下:
$(@D) 代表依赖文件的目录部分
$(@F) 代表依赖文件的文件名(除去目录部分)
对于其他的自动化变量有相同的用法,具体细节可参考GNU make官方文档。网址如下:
http://www.gnu.org/software/make/manual/make.html

六.伪目标

伪目标不代表一个真正的文件。系统不会主动执行伪目标,只有手动指定伪目标之后,才进行相应的操作。如下:

clean:
    rm -rf *.o

考虑如下情况,当工作目录下刚好有一个clean文件,没有依赖,此时目标clean被认为了最新,不去执行相应的操作,因此不符合我们的需求,因此,可以强制将clean定义为伪目标,使用.PHONY特殊目标,如下:

.PHONY : clean
clean:
    rm -rf *.o

这样就避免了冲突。

七.依赖的类型

在上面介绍的所有规则中,依赖可以分为两种,一种是:一旦依赖比目标新则更新目标,另外一类是:当依赖比目标新,不更新目标,只有当目标没有的时候,才去更新目标。第一种依赖就是前面介绍的普通依赖,后面这种依赖称为,命令依赖,即当目标没有的时候,给命令更新目标的时候用。
命令依赖以|开头,举例如下:

libs := bar.a
test:foo.o | $(libs)
    $(CC) $(CFLAGS) $< -o $@ $(libs)

当foo.o比test新,那么命令就会执行,反之当bar.o比test新,并不会执行命令。

七.其他的使用技巧

一.通配符的使用
我们可以在目标和依赖中使用%来进行相应的模式匹配,同样我们也可以使用通配符在目标和依赖中进行更加灵活的匹配,如下

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

该规则指定了依赖当前目录下的所有c文件
可以使用的通配符有三种:*,?,[]
这三种可以使用在规则的目标和依赖中,也可以使用在规则的命令中,谨记不能使用在其他地方,比如变量的定义,如下:

objects := *.o

objects的值为*.o,并不是所有的.o文件。

foo:$(objects)
    $(cc) -o foo $(CFLAGS) $(objects)

当工作目录下没有.o文件的时候,则会提示:没有创建*.o文件的规则。改进方法如下:

objects := $(patsubst %.c,%.o,$(wildcard *.c))

wildcard函数表示在工作目录下,使用通配符进行查找相应的文件,并返回。patsubst函数进行相应的替换并返回。
objects的值,则根据下面的步骤进行产生:
1.取得工作目录下所有的.c文件
2.将对应的.c文件替换成.o,并返回给objects变量。
二.VPATH,vpath,GPATH的使用

VPATH = src:../headers

表示的是:当在工作目录下没有找到相应的文件的时候,可以在VPATH里面指定的目录下进行查找,上例中指定了两个目录,一个为src,一个为headers,多个目录使用冒号分割。

vpath 模式字符串 directory

表示的是:可以将符合模式字符串的文件,在directory文件夹中进行寻找,例子如下:

vpath %.h ../headers

表示的是:当在工作目录中没有找到某个.h文件的时候,可以在../headers文件夹下进行查找

vpath 模式字符串

表示:清楚之前为模式字符串进行的相应的文件夹设置。

vpath

表示清楚所有已被设置的文件搜索路径。

当一个目标没有在工作目录下,但是在vapth,或者VPATH进行目录搜索之后被找到,当需要更新目标的时候,此时,更新的目标会被放置在工作目录下,如果想要放置在以前找到的目录下,需要使用GPATH变量,让其值指定该目录。
三.库文件通过目录搜索得到
程序连接使用的静态库,动态库,同样可以使用目录搜索进行找到。这个需要在依赖列表中,进行如下的写法。-lname,name为库的名字,而这个库的名字,不包括前面的lib和后面的后缀。
举例如下:

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

详细过程如下:
1.make会在当前目录下寻找libcurses.so,如果没有,
2.搜索vpath,VPATH路径下,还是没有,
3.搜寻系统默认的库文件存放目录,顺序为:/lib,/usr/lib,/usr/local/lib
4.如果还是没有找到,则按上面的步骤搜寻libcurses.a库文件。
四.合理使用伪目标,提高编译效率

SUBDIRS := foo bar baz
subdirs :
    for dir in $(SUBDIRS) ;do \
$(MAKE) -C $$dir;\
done

当执行subdirs目标时,会依次进行子目录然后运行make,当我们使用-j参数之后,这个还是呈现一个单线程的执行。我们可以进行如下修改

SUBDIRS = foo bar baz
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS)
$(SUBDIRS):
    $(MAKE) -C $@
foo: baz

最后一条规则,表示baz比foo先生成。用来限制子目录的make顺序。
此时,当执行subdirs目标的时候,会去检测foo bar baz,发现都没有的时候,会使用

$(SUBDIRS):
    $(MAKE) -C $@

规则进行相应的生成,此时可以发挥了make的多线程功能了,因为这三个文件,可以放在不同的线程中进行生成,从而避免了shell命令行下,for的单线程操作,可以更好的发挥电脑性能。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值