15、make中的隐式规则

如果同一个目标的命令拆分的写到不同地方,会发生什么?

下面的程序怎么执行?为什么?

makefile中出现同名目标时:

依赖:所有的依赖将合并在一起,成为目标的最终依赖。

命令:当多处出现同一目标时,make发出警告。所有之前定义的命令被最后定义的命令取代。

注意事项:

当使用include关键字包含其他文件时,需要确保被包含文件中的同名目标只有依赖,没有命令。否则,同名目标的命令将被覆盖。

什么是隐式规则(built-in rules)?

make提供了一些常用的,例行的规则实现。

当相应目标的规则未提供时,make尝试使用隐式规则。

下面的makefile能成功编译吗?

@echo "$(.VARIABLES)"

所有预定义变量的名字。

cc:第一个c是C语言,第二个是compler第一个字母。cc是unix下标准编译器,查看cc

ls -l /user/bin/cc 

ls -l /etc/alternatives/cc

/etc/alternatives/cc -> /usr/bin/gcc  /所以cc就是gcc了

初探隐式规则:

make提供了生成目标文件的隐式规则。

隐式规则会使用预定义变量完成编译工作。

改变预定义变量将部分改变隐式规则的行为。

当存在自定义规则时,不在使用隐式规则。

小结:

当多处出现同一命令时,只有最后定义的命令有效、

make提供了一系列的隐式规则可使用。

当makefile中未定义规则时,尝试使用隐式规则。

隐式规则中可能使用make中的预定义变量。

改变预定义变量可部分改变预定义规则的行为。

16、隐式规则下

当make发现目标的依赖不存在时:

尝试通过依赖名逐一查找隐式规则。

并且通过依赖名推导可能需要的源文件。

app.out : main.o func.o  1、发现目标依赖不存在。2、查找隐式规则,并自动推导出:func.o  func.c 3、根据文件后缀自动推导编译命令: cc -c -o func.o func.c

    $(CC) -o $@ $^

隐式规则的副作用

编译行为难以控制:大量使用隐式规则则可能产生意想不到的编译行为。

编译效率低下:make从隐式规则和自定义规则中选择最终使用的规则。

隐式规则链:

当依赖的目标不存在时,make会极力组合各种隐式规则对目标进行创建,进而产生意料之外的编译行为。

例:

需要名为N.o的目标:N.y->N.c->N.o 非常复杂了

make提供了多少隐式规则,如何查看隐式规则?

1000个左右

查看隐式规则:

查看所有:make -p

查看具体规则:make -p | grep "%.o"  重定向到grep命令

隐式规则的禁用:

局部禁用:

在makefile中自定义规则。

在makefile中定义模式(如:%.o : %.p)

全局禁用:

make -r

后缀规则简介:

后缀规则是旧式的“模式规则”。通过什么文件产生什么目标

可以通过后缀描述的方式自定义规则。

.cpp.o :(通过cpp文件得到.o文件)

        @echo "suffix rule"

        g++ -o $@ -c $^

通过.cpp文件产生.o文件-->例:func.cpp->fun.o

双后缀规则:

定义一对文件后缀(依赖文件后缀和目标文件后缀)

如: .cpp.o<-->%.o : %.cpp

单后缀规则:

定义单个文件后缀(源文件后缀)

如: .c<-->% : %.c 产生任意后缀

关于后缀规则的注意事项:

后缀规则中不允许有依赖。

后缀规则必须有命令,否则无意义。

后缀规则将逐步被模式规则取代。

小结:

隐式规则可能造成意想不到的行为。

在实际工程项目中尽量不使用隐式规则。

后缀规则时一种旧式的模式规则。

后缀规则将被模式规则取代。

17、make中的路径搜索

问题:

在实际工程项目中,所有的源文件和头文件都放在一个同一个文件夹中吗?

常用的源码管理方式:

项目中的makefile必须能够正确的定位源文件和依赖的文件,最终编译产生可执行程序。

下面的makefile能够成功编译吗?

特殊的预定义变量VPATH(全大写)

VPATH变量的值用于指示make如何查找文件。

不同文件夹可作为VPATH的值同时出现。

文件夹的名字之间需要使用分隔符进行区分。

例1:VPATH : =Inc src (空格)

例2:VPATH : =inc;src (分号)

例3:VPATH : =inc:src (冒号)

make对于VPATH值的处理方式:

当前文件夹找不到需要的文件时,VPATH会被使用。

make会在VPATH指定的文件夹中依次搜索文件。

当多个文件夹存在同名文件时,选择第一次搜索到的文件。  

注意事项:

VPATH只能决定make的搜索路径,无法决定命令的搜索路径。

对于特定的编译命令(gcc),需要独立指定编译搜索路径。

gcc -I(编译选项,指定gcc的头文件搜索路径) include-path(路径参数,具体需要gcc搜索的路径)

OBJS :=func.o main.o
INC :=inc
SRC :=src
VPATH :=$(INC) $(SRC) 
CFLAGS :=-I $(INC)
hello.out :$(OBJS)
@gcc -o $@ $^
@echo "target file ==>$@"
$(OBJS) :%.o :%.c func.h

@gcc $(CFLAGS) -o $@ -c $<

VPATH存在的问题:

当inc文件夹中意外出现源文件(C/Cpp文件),那么可能产生编译错误。

替换方案:vpath关键字(全小写)

为不同类型的文件指定不同的搜索路径。

语法: 

在Directory中搜索符合Pattern的规则的文件。

vpath Pattern Directory

例:vpath %.h inc

        vpath %.c src

也可以取消搜索规则:

取消已经设置的某个搜索规则

vpath Pattern

例:vpath %.h inc #在inc中搜索.h文件

       vpath %.h      #不再到inc中搜索.h文件

取消所有已经设置的规则:vpath

OBJS :=func.o main.o
INC :=inc
SRC :=src
vpath %.h $(INC)
vpath %.c $(SRC)
CFLAGS :=-I $(INC)
hello.out :$(OBJS)
@gcc -o $@ $^
@echo "target file ==>$@"
#vpath %.h
$(OBJS) :%.o :%.c func.h

@gcc $(CFLAGS) -o $@ -c $<

小结:

VPATH变量用于指示make如何查找文件。

make会在VPATH指定的文件夹中依次搜索文件。

vpath关键字可以为不同类型的文件指定不同的搜索路径。

vpath比VPATH更灵活易用,可动态设置/取消搜索路径。

18、路径搜索下

问题一:

当VPATH和vpath同时出现时,make会如何处理?

下面的项目中会选择哪一个文件编译?

VPATH :=src1
CFLAGS :=-I inc
vpath %.c src2
vpath %.h inc
app.out :func.o main.o
@gcc -o $@ $^
@echo "target file ==>$@"
%.o :%.c func.h
@gcc $(CFLAGS) -o $@ -c $<

试验结论:

make首先在当前文件夹搜索需要的文件。

搜索失败:

make优先在vpath关键字指定的文件夹中搜索目标文件。

当vpath搜索失败时,转而搜索VPATH指定的文件夹。

VPATH :=src1 src3

vpath %.c src2

==>src2 -> src1 ->src3

问题二:

当使用vpath对同一个Pattern指定多个文件夹时,make会如何处理。

下面的项目会选择哪一个文件进行编译?

CFLAGS :=-I inc
vpath %.c src2
vpath %.c src1
vpath %.h inc
app.out :func.o main.o
@gcc -o $@ $^
@echo "target file ==>$@"
%.o :%.c func.h
@gcc $(CFLAGS) -o $@ -c $<

实验结论:

make首先在当前文件夹搜索需要的文件。

如果失败:

make以自上而下的顺序搜索vpath指定的文件夹、

当找到目标文件,搜索结束。

vpath %.c src3

vpath %.c src1

vpath %.c src2

==>src3->src1->src2

vpath不使用隐式规则,VPATH会使用隐式规则

VPATH :=src1 

vpath %.c src2

当把.c文件改为.cpp文件时,vpath不会调用隐式规则从.app到.o,而VPATH会调用隐式规则从.app到.o的转换。

最好用vpath。

问题三:

通过VPATH指定搜索路径后,make如何决定目标文件的最终位置?

app.out会生成在那个文件夹中?

把app.out移动到src中,

改动源文件后重新make

还是会在当前文件夹中创建新的app.out。

试验结论:

当app.out完全不存在:make在当前文件下创建app.out

当src文件夹中存在app.out:所有目标和依赖的新旧关系不变,make不会重新创建app.out(最新的)。当依赖文件被更新,make在当前文件夹下创建app.out。

问题:当依赖改变时,如何使得src下的app.out被更新?

解决方案:

使用GPATH特殊变量指定目标文件夹。

GPATH := src

当app.out完全不存在:make默认在当前文件夹创建app.out。

当app.out存在于src(把app.out拖动到src),且依赖文件被更新:make在src中创建app.out。

但凡使用了VPATH,考虑一下是否要用GPAHT.

GPATH :=src
VPATH :=src
CFLAGS :=-I inc
app.out :func.o main.o
@gcc -o $@ $^
@echo "target file ==>$@"
%.o :%.c inc/func.h

@gcc $(CFLAGS) -o $@ -c $<

工程项目中的几点建议:

尽量使用vpath为不同文件指定搜索路径。

不要在源码文件夹中生成目标文件。

为编译得到的结果创建独立的文件夹。

避免VPATH和GPAHT特殊变量的使用。

19、路径搜索综合示例

需求分析:

工程项目中不希望源码文件夹在编译时被改动(只读文件夹)

在编译时自动创建文件夹(build)用于存放编译结果。

编译过程中能够自动搜索需要的文件。

makefile易于扩展,能够复用于相同类型的项目。

支持调试版本的编译选项。

工具原料:

$(wildcard $(DIR)/_pattern)

获取$(DIR)文件夹中满足_pattern的文件。

$(notdir _names)

去除_names中每一个文件名的路径前缀。

$(patsubst _pattern, replacement, _text)

将 _text中符合_pattern的部分替换为replacement。

关键技巧:

1、自动获取源文件列表(函数调用)

SRCS :=$(wildcard src/*.c)

2、根据源文件列表生成目标文件列表(变量的值替换)

OBJS :=$(SRCS: .c=.o)

3、替换每一个目标文件的路径前缀(函数调用)src前缀换为build前缀

OBJS :=$(patsubst src/%,build/%, $(OBJS))

编译规则的依赖:图

all->依赖于(build,app.out) app.out->(build,%.o)   %.o->build(生成到)

自动生成文件夹build。

可复用性和可扩展性需要通过变量得到

cd make  到make中

cd ..   返回上一级目标

.PHONY : all clean
DIR_BUILD :=build
DIR_SRC :=src
DIR_INC :=inc
CC :=gcc
CFLAGS := -I $(DIR_INC)
MKDIR :=mkdir
RM :=rm -fr
APP := $(DIR_BUILD)/app.out
HDRS :=$(wildcard $(DIR_INC)/*.h)
HDRS :=$(notdir $(HDRS))
OBJS :=$(wildcard $(DIR_SRC)/*.c)
OBJS :=$(OBJS:.c=.o)
OBJS :=$(patsubst $(DIR_SRC)/%, $(DIR_BUILD)/%, $(OBJS))
vpath %.h $(DIR_INC)
vpath %.c $(DIR_SRC)
all:$(DIR_BUILD) $(APP)
@echo "target file==>$(APP)"
$(DIR_BUILD):
$(MKDIR) $@
$(APP) : $(OBJS)
$(CC) -o $@ $^
#touwenjian zuowei yilai wenjian
$(DIR_BUILD)/%.o : %.c $(HDRS) 
$(CC) $(CFLAGS) -o $@ -c $<
clean :
$(RM) $(DIR_BUILD)

扩展性差一点,.cpp就不可以了。

在次使用变量、

LFLAGS 链接选项

-g 调试版本的程序

makefile可以复用到别的项目中,改变名字  .c改为 .cpp gcc改为g++

用到了多线程,链接需要指定链接选项,指定需要使用多线程库。

改动 LFLAGS :=-pthread  链接到多线程库

.PHONY : all clean
DIR_BUILD :=build
DIR_SRC :=src
DIR_INC :=inc

TYPE_INC := .h
TYPE_SRC := .c
TYPE_OBJ := .o

CC :=gcc
LFLAGS :=

CFLAGS := -I $(DIR_INC)
ifeq ($(DEBUG),true)
CFLAGS += -g
endif
MKDIR :=mkdir
RM :=rm -fr
APP := $(DIR_BUILD)/app.out
HDRS :=$(wildcard $(DIR_INC)/*$(TYPE_INC))
HDRS :=$(notdir $(HDRS))
OBJS :=$(wildcard $(DIR_SRC)/*$(TYPE_SRC))
OBJS :=$(OBJS:$(TYPE_SRC)=$(TYPE_OBJ))
OBJS :=$(patsubst $(DIR_SRC)/%, $(DIR_BUILD)/%, $(OBJS))
vpath %$(TYPE_INC) $(DIR_INC)
vpath %$(TYPE_SRC) $(DIR_SRC)
all:$(DIR_BUILD) $(APP)
@echo "target file==>$(APP)"
$(DIR_BUILD):
$(MKDIR) $@
$(APP) : $(OBJS)
$(CC) $(LFLAGS) -o $@ $^
#touwenjian zuowei yilai wenjian
$(DIR_BUILD)/%$(TYPE_OBJ) : %$(TYPE_SRC) $(HDRS) 
$(CC) $(CFLAGS) -o $@ -c $<
clean :

$(RM) $(DIR_BUILD)

值得斟酌的问题:

对于规模较小的项目,makefile中是否也需要使用自动生成依赖关系的解决方案?

.o依赖于.h文件,h文件改动,o文件需要重新编译生成。

小项目不用,效率换维护

小结:

工程项目中不希望源码文件夹在编译时被改动。

模式规则的灵活运用使得makefile具有复用性。

变量的灵活运用使得makefile具有扩展性。

规模较小的项目没必要使用自动生成依赖关系的解决方案。

规模较小的项目可以直接让源文件依赖于头文件(易于维护)。

猜你喜欢

转载自blog.csdn.net/ws857707645/article/details/80794749