从头开始写项目Makefile(八):模式规则

【版权声明:转载请保留出处:blog.csdn.net/gentleliu。Mail:shallnew at 163 dot com】

上一节讲到目录创建成功,目标文件没有生产到对应目录下,这里我们先给目标文件加上对应目录,这样的话产生对应的目标文件会直接生成到对应目录。我们先给库文件目标和可执行文件目标加上路径,如下:

lib : $(OBJDIR) $(LIBDIR)/$(SRC_LIB)

bin : $(OBJDIR) $(BINDIR)/$(SRC_BIN)

$(OBJDIR) :
>[email protected] "   MKDIR $(notdir [email protected])..."
>[email protected] -p [email protected]

ifneq ($(SRC_BIN),)
$(BINDIR)/$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o [email protected] $^ $(LDFLAGS)
endif

ifneq ($(SRC_LIB),)
$(LIBDIR)/$(SRC_LIB) : $(SRC_OBJ)
>---$(AR) rcs [email protected] $^
>---cp [email protected] $(SRC_BASE)/libs
endif

此时再执行make,完成后查看build目录树:

build/
└── unix_dbg
    ├── bin
    │   └── target_bin
    ├── lib
    │   ├── libipc.a
    │   └── libtools.a
    └── obj
        ├── ipc
        ├── main
        └── tools

可以看到,生成的目标是在对应目录下。我们乘胜追击,把.o文件也将其修改了。我们之前的每个模块Makefile大致是这样写的:

SRC_BASE = ../..                                                                             

CFLAGS +=
CPPFLAGS += -I. -I./inc -I$(SRC_BASE)/include                                                

# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_LIB = xx.a                                                                           

include $(SRC_BASE)/Makefile.rule

其中SRC_OBJ在此处给出,然后再在Makefile.rule中使用,此处的.o文件会在.c文件相同目录下生成,所以我们现在需要将.o文件加上路径,由于取得路径是在Makefile.rule里面,所以我们可以统一在Makefile.rule里面给变量SRC_OBJ赋值,大致如下:

SRC_OBJ = $(patsubst %.c, $(OBJDIR)/%.o, $(notdir $(SRC_FILES)))

这里用到函数patsubst、notdir,关于函数会在后面讲到。这样.o文件作为目标生成之后就会生成到相应目录里面了。

此时再编译:

# make
make[1]: Entering directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make[1]: *** No rule to make target `../../build/unix_dbg/obj/ipc/ipc.o', needed by `../../build/unix_dbg/lib/libipc.a'.  Stop.
make[1]: Leaving directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make: *** [ipc] Error 2
#

发现出错了,并且是在生成目标文件ipc.o时没有成功,查看build目录树也没有生成.o文件。为什么会生成失败呢?

我们没有给出生成.o目标的规则,之前可以生成是因为make有通过隐含规则来自动推导的能力(这个之前有讲到,链接过去)。在我们没有修改之前,生成.o通过隐含规则来完成:

%.o: %.c
#  commands to execute (built-in):
>---$(COMPILE.c) $(OUTPUT_OPTION) $<

因为所有的.o目标符合该规则,所以会自动推导生成.o文件。我们现在在..o前面加上路径后没有符合生成.o的隐含模式规则了,所以就没有生成该文件,导致编译出错。那怎么办呢?没有隐含模式规则,我们可以自己写符合生成该目标的模式规则。

模式规则类似于普通规则,只是在模式规则中,目标文件是一个带有模式字符“%”的文件,使用模式来匹配目标文件。在目标文件名中“%”匹配的部分称为“茎”。使用模式规则时,目标文件匹配之后得到“茎”,依赖根据“茎”产生对应的依赖文件,这个依赖文件必须是存在的或者可被创建的。

所以,我们增加一条模式规则如下:

$(OBJDIR)/%.o : %.c
>---$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o [email protected]

该模式规则中目标文件是$(OBJDIR)/%.o,那么现在有了符合生成我们需要的.o文件的规则了,编译一下:

# make
make[1]: Entering directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make[1]: *** No rule to make target `../../build/unix_dbg/obj/ipc/ipc.o', needed by `../../build/unix_dbg/lib/libipc.a'.  Stop.
make[1]: Leaving directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make: *** [ipc] Error 2
#

发现还是不对,不是已经增加了模式规则了吗,为何还是没有生成.o文件。

我们这里先说说静态模式规则:

一个规则中可以有多个目标,规则所定义的命令对所有的目标有效。一个具有多目标的规则相当于多个规则。 规则的命令对不同的目标的执行效果不同, 因为在规则的命令中可能使用了自动化变量 “[email protected]” 。 多目标规则意味着所有的目标具有相同的依赖文件。多目标通常用在以下两种情况:虽然在多目标的规则中, 可以根据不同的目标使用不同的命令 (在命令行中使用自动化变量 “[email protected]” )。但是, 多目标的规则并不能做到根据目标文件自动改变依赖文件 (像上边例子中使用自动化变量“[email protected]”改变规则的命令一样) 。需要实现这个目的是,要用到make的静态模式。

静态模式规则是这样一个规则:规则存在多个目标, 并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。静态模式规则比多目标规则更通用, 它不需要多个目标具有相同的依赖。但是静态模式规则中的依赖文件必须是相类似的而不是完全相同

的。静态模式规则语法如下:

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

比如下面是一个静态模式规则:

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o [email protected]

该规则描述了所有的.o文件的依赖文件为对应的.c文件,对于目标“foo.o” ,取其茎“foo”替代对应的依赖模式“%.c”中的模式字符“%”之后可得到目标的依赖文件“foo.c”。这就是目标“foo.o”的依赖关系“foo.o: foo.c”,规则的命令行描述了如何完成由“foo.c”编译生成目标“foo.o” 。命令行中“$<”和“[email protected]”是自动化变量,“$<” 表示规则中的第一个依赖文件, “[email protected]” 表示规则中的目标文件。上边的这个规则描述了以下两个具体的规则:

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

(注:该示例与其相关描述摘抄于互联网,描述很不错,估计比我讲的详细)

那静态模式规则和普通的模式规则(非静态模式规则)有什么去区别呢?两者都是用目标模式和依赖模式来构建目标的规则中的文件依赖关系,两者不同的地方是 make 在执行时使用它们的时机。

静态模式规则只能用在规则中明确指出的那些文件的重建过程中。不能用在除此之外的任何文件的重建过程中,并且它对指定的每一个目标来说是唯一的。如果一个目标存在于两个规则,并且这两个规则都定义了命令, make 执行时就会提示错误。

非静态模式规则可被用在任何和它相匹配的目标上,当一个目标文件同时符合多个目标模式时,make将会把第一个目标匹配的模式规则作为重建它的规则。

那有没有想过如果我们指定了模式规则后,那还有隐含规则呢,那怎么选择执行哪一个模式规则呢?Makefile中明确指定的模式规则会覆盖隐含模式规则。就是说如果在Makefile中出现了一个对目标文件合适可用的模式规则,那么make就不会再为这个目标文件寻找其它隐含规则,而直接使用在Makefile中出现的这个规则。在使用时,明确规则永远优先于隐含规则。

我们继续说之前的那个问题,我们定义了模式规则后还是没有生成.o文件,我们现在将其改为静态规则再试试就看,如下:

$(SRC_OBJ) : $(OBJDIR)/%.o : %.c
>---$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o [email protected]

执行后:

# make
make[1]: Entering directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make[1]: *** No rule to make target `ipc.c', needed by `../../build/unix_dbg/obj/ipc/ipc.o'.  Stop.
make[1]: Leaving directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make: *** [ipc] Error 2
#

发现提示没有文件ipc.c,这说明没有生成.o的原因是没有.c文件,我很好奇的是为何使用非静态模式为何不提示呢?(还没搞懂,再研究研究,知道的可以给个提示哈~~)

缺少依赖文件,为何没有*.c文件,仔细想想我们的.o文件没有和.c文件在同一目录。在我们工程中,将源代码和二进制文件(.o 文件和可执行文件)安排在不同的目录来进行区分管理。这种情况下,我们可以使用 make 提供的目录搜索依赖文件功能。该功能在下一节讲述,这一节说的够多了,有点累了。可惜最终还是没有给出一个可用的Makefile,在下一节将会给出。

时间: 2024-12-25 16:06:10

从头开始写项目Makefile(八):模式规则的相关文章

从头开始写项目Makefile(二):自动推导

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 上一节的Makefile勉强可用,但还写的比较繁琐,不够简洁.对每一个.c源文件,都需要写一个生成其对应的.o目标文件的规则,如果有几百个或上千个源文件,都手动来写,还不是很麻烦,这也不够自动化啊. 这样,我们把生成.o目标文件的规则全部删除掉,就是这样一个Makefile文件: target_bin : main.o debug.o ipc.o timer.o

从头开始写项目Makefile(十):make内嵌函数及make命令显示

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 这一节我们讲一下make的函数,在之前的章节已经讲到了几个函数:wildcard.patsubst.notdir.shell等.一般函数的调用格式如下: $(funcname arguments) 或 $(funcname arguments) 其中funcname是需要调用函数的函数名称,应该是make内嵌函数:arguments是函数参数,参数和函数名之间使

从头开始写项目Makefile(九):目录搜索

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 在一个较大的工程中,一般会将源代码和二进制文件(.o 文件和可执行文件)安排在不同的目录来进行区分管理.这种情况下,我们可以使用 make 提供的目录搜索依赖文件功能(在指定的若干个目录下自动搜索依赖文件).在Makefile中,使用依赖文件的目录搜索功能.当工程的目录结构发生变化后,就可以做到不更改 Makefile的规则,只更改依赖文件的搜索目录. 在我们上

从头开始写项目Makefile(六):参数传递、条件判断、include

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 在多个Makefile嵌套调用时,有时我们需要传递一些参数给下一层Makefile.比如我们在顶层Makefile里面定义的打开调试信息变量DEBUG_SYMBOLS,我们希望在进入子目录执行子Makefile时该变量仍然有效,这是需要将该变量传递给子Makefile,那怎么传递呢?这里有两种方法: 1.     在上层Makefile中使用"export&qu

从头开始写项目Makefile(四):伪目标

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 一般情况下,Makefile都会有一个clean目标,用于清除编译过程中产生的二进制文件.我们在第一节的Makefile就用到了这个 clean目标,该目标没有任何依赖文件,并且该目标对应的命令执行后不会生产clean文件. 像这种特点目标,它的规则所定义的命令不是去创建文件,而仅仅通过make指定目标来执行一些特定系统命令或其依赖为目标的规则(如all),称为

从头开始写项目Makefile(五):嵌套执行

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 在大一些的项目里面,所有源代码不会只放在同一个目录,一般各个功能模块的源代码都是分开的,各自放在各自目录下,并且头文件和.c源文件也会有各自的目录,这样便于项目代码的维护.这样我们可以在每个功能模块目录下都写一个Makefile,各自Makefile处理各自功能的编译链接工作,这样我们就不必把所有功能的编译链接都放在同一个Makefile里面,这可使得我们的Ma

从头开始写项目Makefile(三):变量的使用

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 仔细研究我们的之前Makefile发现,我们还有改进的地方,就是此处: target_bin : main.o debug.o ipc.o timer.o tools.o >---gcc -o target_bin main.o debug.o ipc.o timer.o tools.o 如果增加一个源文件xx.c的话,需要在两处或多处增加xx.o文件.我们可以

从头开始写项目Makefile(一):基本规则

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 一般一个稍大的linux项目会有很多个源文件组成,最终的可执行程序也是由这许多个源文件编译链接而成的.编译是把一个.c或.cpp文件编译成中间代码.o文件,链接是就使用这些中间代码文件生成可执行文件.比如在当前项目目录下有如下源文件: # ls common.h debug.c debug.h ipc.c ipc.h main.c tags timer.c ti

从头开始写项目Makefile(零):前言

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 一不小心工作三年了,也就是写了三年代码了,码农生活过的真快.最近发现我们项目的Makefile不够好,于是着手改了一下,以前Makefile写好后就很少动它了,直接在项目之间拷来拷去直接拿来用,这次重新修改项目Makefile,发现自己又学到不少东西,于是乎决定总结一下记录下来与各位苦逼的程序猿一道分享一下. 其实各大论坛博客已经有很多关于Makefile的文章