makefile 文件 (​ http://blog.csdn.net/ruglcc/article/details/7814546/ )-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

makefile 文件 (​ http://blog.csdn.net/ruglcc/article/details/7814546/ )

简介:

makefile三要素:目标,依赖,命令

make -f makefilename   指定makefile文件

(makefile写多行命令时,使用换行符“\”)

一、makefile基础

最终目标:makefile的最终目标只有一个,makefile文件以更新最终目标为最终任务,并且只执行与更新最终目标有关的规则。

隐晦规则:自动将.O作为目标,.C作为依赖执行gcc编译命令。隐晦规则只适用于.H和.C文件都在同一个目录下,并且生产的.o文件也只能在该目录下。否则隐晦规则自动推导的编译规则找不到头文件,它不会加-I选项,而vpath也无法指定gcc的编译路径。(只适用于小型文件编译)

目标与依赖的文件属性:在makefile中,所有的目标和依赖都是文件,伪目标也是文件。伪目标和实体目标没有区别,.PHONY只是指明了该目标文件在目录下始终不存在(即使存在也认为不存在,这样就可以永远执行该目标,企图生成该目标,该伪规则执行完之后,无论是否生成了该目标,make都认为已经生成了该目标,伪目标文件的更新时间是该伪规则执行完毕的那一刻)

makefile中的依赖链及其执行顺序:从最终目标开始,make按树形结构以最终目标为根节点检查每一条规则的目标文件和依赖文件更新时间,如果目标文件比依赖旧(说明依赖文件被修改过),那么标记该规则节点需要被执行;再用同样的方法检查以当前依赖文件作为目标的规则是否需要被执行,如果是,标记该规则; ...;直到检查完该最终目标树,最后执行从每条分支最深标记节点到根节点的所有路径上的节点规则。依赖链指明了各条规则执行的顺序,从而指明了shell命令执行的顺序或者说文件生成的顺序。


(1)没有依赖的目标文件的规则检测

    如果目录下存在该目标文件,那么认为该目标文件时最新的,该规则永远不会被执行;如果该目标文件不存在,那么始终执行该规则,企图生成该目标文件。.PHONY关键字作用:强制认为目录下不存在该目标文件,使得make总是执行该规则,企图重新生成该目标。(伪规则执行完之后,无论是否生成了该伪目标,make都认为已经生成了该目标,伪目标文件的更新时间是该伪规则执行完毕的那一刻,这样就可以用该更新时间去执行依赖伪目标的规则)

(2)存在依赖的目标文件的规则检测

    如果目录下不存在该目标文件,那么make执行该规则企图生成该文件(但是未必一定要生成,取决于作者);如果存在该目标文件那么检查更新,企图生成该文件。

makefile文件的执行步骤(include重建makefile)

1.依次读取变量“MAKEFILES”定义的makefile文件列表

2.读取工作目录下的makefile文件(根据命令的查找顺序GNUmakefile,makefile,Makefile首先找到哪个就读取哪个)

3.依次读取工作目录makefile文件中使用指示符include包含的文件

4.查找并重建所有已读取的文件的规则(如果存在一个目标是当前include读取的某一个文件,则先执行此规则并重建当前makefile文件:从第一步开始重新执行

5.初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支(先执行makefile内置函数和变量代替然后再执行%通配)

6.根据"最终目标"与其他目标的依赖关系建立依赖关系链表

7.执行除"终极目标"以外的所有依赖目标的规则(规则中如果依赖文件中任一个文件的时间戳比目标文件新,则使用规则所定义的命令重建目标文件,不管是伪目标还是实体目标,重建时间为规则执行完的时刻)

8.执行"终极目标"所在的规则


通配符 % :代表非空字符串,用于模式规则

变量与赋值:

$() 和${}是一样的makefile变量

$${} shell变量

递归展开赋值  : A=2 B=$(A) A=3  则最终B==3

直接展开赋值: A:=2 B:=$(A) A:=3  则最终B==2


一般情况下,只使用直接展开赋值

变量的替换引用:

//foo := a.o b.o c.o 

//bar := $(foo:.o=.c) 

//“bar”的值就为“a.c b.c c.c”

变量追加

objects += another.o 将使用空格隔开   相当于objects := $(objects) another.o

a=b c ; a+=d后a的值为b c d

b?=word  如果变量b没有定义,则定义并赋值为word


在makefile中使用shell的流控制语句

All:

  for i in $(filename);do gcc -c $$i.c -o output/$$i.o;done

clean:

  rm -f *.o


$${} shell变量

${}  makefile变量


makefile条件判断

        使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。
    示例:下面的例子,判断$(CC)变量是否“gcc”,如果是的话,则使用GNU函数编译目标。
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
    $(CC) -o foo $(objects) $(libs_for_gcc)
else
    $(CC) -o foo $(objects) $(normal_libs)
endif

预定义变量:

AR 归档维护程序的名称,默认值为 ar。

ARFLAGS 归档维护程序的选项。

AS 汇编程序的名称,默认值为 as。

ASFLAGS 汇编程序的选项。

CC C 编译器的名称,默认值为 cc。

CCFLAGS C 编译器的选项。

CPP C 预编译器的名称,默认值为 $(CC) -E。

CPPFLAGS C 预编译的选项。

CXX C++ 编译器的名称,默认值为 g++。

CXXFLAGS C++ 编译器的选项。

FC FORTRAN 编译器的名称,默认值为 f77。

FFLAGS FORTRAN 编译器的选项。

vpath:指定不同文件的各自路径目录和扩展目录,可以使用‘%’

VPATH:指定路径搜索路径


#注释符

@用于命令前,编译时,该命令不回显

[TAB] 命令前必须加[TAB]

自动化变量(位置变量)

在模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。

    自动化变量就是完成这个功能的。在前面,我们已经对自动化变量有所提涉,相信你看到这里已对它有一个感性认识了。所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。


下面是所有的自动化变量及其说明:

$@ :(常用)表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。

$% :仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件

(Unix下是[.a],Windows下是[.lib]),那么,其值为空。

$< :(常用)依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

$? :所有比目标新的依赖目标的集合。以空格分隔。

$^ :(常用)所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。

$+ :这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。

$* :这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。这个变量对于构造有关联的

文件名是比较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么"$*"就是除了后缀的那一部分。例如:如

果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使

用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。当你希望只对更新过的依赖文件进行操作时,"$?"在显式规则中

很有用,例如,假设有一个函数库文件叫"lib",其由其它几个object文件更新。那么把object文件打包的比较有效率的Makefile规则是:

    lib : foo.o bar.o lose.o win.o

            ar r lib $?

在上述所列出来的自动量变量中。四个变量($@、$<、$%、$*)在扩展时只会有一个文件,而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或是

      在当前目录下的符合模式的文件名,只需要搭配上"D"或"F"字样。这是GNU make中老版本的特性,在新版本中,我们使用函数"dir"或"notdir"就可以做到了。"D"的含义就是Directory,就是目录,"F"的含义就是File,就是文件。

下面是对于上面的七个变量分别加上"D"或是"F"的含义:

$(@D) :表示"$@"的目录部分(不以斜杠作为结尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录)。


$(@F) :表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o","$(@F)"相当于函数"$(notdir $@)"。

"$(*D)" "$(*F)" :和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子,"$(*D)"返回"dir",而"$(*F)"返回"foo"

"$(%D)" "$(%F)" :分别表示了函数包文件成员的目录部分和文件部分。这对于形同"archive(member)"形式的目标中的"member"中包含了不同的目录很有用。

"$(<D)" "$(<F)" :分别表示依赖文件的目录部分和文件部分。

"$(^D)" "$(^F)" :分别表示所有依赖文件的目录部分和文件部分。(无相同的)

"$(+D)" "$(+F)" :分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)

"$(?D)" "$(?F)" :分别表示被更新的依赖文件的目录部分和文件部分。

    最后想提醒一下的是,对于"$<",为了避免产生不必要的麻烦,我们最好给$后面的那个特定字符都加上圆括号,比如,"$(< )"就要比"$<"要好一些。

还得要注意的是,这些变量只使用在规则的命令中,而且一般都是"显式规则"和"静态模式规则"(参见前面"书写规则"一章)。其在隐含规则中并没有意义。

三.include的作用

通常指示符“include”用在以下2个场合:

  (1)有多个不同的程序,由不同目录下的几个独立的Makefile来描述其创建或者更新规则。它们需要使用一组通用的变量定义或者模式规则。通用的做法是将这些共同使


用的变量或者模式规则定义在一个文件中,在需要使用的Makefile中使用指示符“include”来包含此文件。

  (2)当根据源文件自动产生依赖文件时;我们可以将自动产生的依赖关系保存在另外一个文件中,主Makefile使用指示符“include”包含这些文件。这样的做法比直接在


主Makefile中追加依赖文件的方法要明智的多。将指定文件复制到当前文件中,可用于自动生成.c文件的依赖文件


include对makefile的重建作用:make首先读取所有include包含的文件,然后检查规则,如果存在一个目标是当前include包含的某一个文件,则先执行此规则,并重新包含该include文件,重建当前内存中的makefile文件


使用方法:include filename


四.makefile内置函数

调用方式: ${funname var1,var2,...}

4.1文本处理函数

$(subst FROM,TO,TEXT) 字符串替换函数   把字串“TEXT”中的“FROM”字符替换为“TO”

$(subst ee,EE,feet on the street) 

结果是新的字符串“fEEt on the strEEt”。  

$(patsubst PATTERN,REPLACEMENT,TEXT)  模式替换函数(以空格为分隔匹配模式) 

$(patsubst %.c,%.o,x.c m.c bar.c) 

结果是 x.o m.o bar.o

$(strip STRINT)  去空格函数—strip。

STR =        a    b c    

LOSTR = $(strip $(STR)) 

结果是“a b c”。 

$(findstring FIND,IN)  查找字符串函数

$(findstring a,a b c) 

$(findstring a,b c) 

第一个函数结果是字“a”;第二个值为空字符。

$(filter PATTERN…,TEXT) 过滤函数

sources := foo.c bar.c baz.s ugh.h 

$(filter %.c %.s,$(sources))

函数返回值为“foo.c bar.c baz.s”。

$(filter-out PATTERN...,TEXT) 反过滤函数

$(sort LIST) 排序函数

$(sort foo bar lose foo) 

返回值为:“bar foo lose”。 

$(word N,TEXT)   取单词函数

$(word 2, foo bar baz) 

返回值为“bar”。 

$(wordlist S,E,TEXT)  取单词函数

$(wordlist 2, 3, foo bar baz) 

返回值为:“bar baz”。 

$(words TEXT) 统计单词数目函数

$(words, foo bar) 

返回值是“2”。

$(firstword NAMES…) 取首单词函数

$(firstword foo bar) 

返回值为“foo”。

4.2 文件名处理函数

$(dir NAMES…)  取目录函数

$(dir src/foo.c hacks) 

返回值为“src/ ./”。 

$(notdir NAMES…) 取文件名函数

$(notdir src/foo.c hacks) 

 返回值为:“foo.c hacks”。 

$(suffix NAMES…)  取后缀函数

$(suffix src/foo.c src-1.0/bar.c hacks) 

返回值为“.c .c”。 

$(basename NAMES…)  取前缀函数

$(basename src/foo.c src-1.0/bar.c /home/jack/.font.cache-1 hacks) 

返回值为:“src/foo src-1.0/bar /home/jack/.font hacks”。 

$(addsuffix SUFFIX,NAMES…) 加后缀函数

$(addsuffix .c,foo bar) 

返回值为“foo.c bar.c”。 

$(addprefix PREFIX,NAMES…)  加前缀函数

$(addprefix src/,foo bar) 

返回值为“src/foo src/bar”。 

$(join LIST1,LIST2) 单词连接函数

$(join a b c , .c .o) 

返回值为:“a.c b.o c”。 

$(wildcard PATTERN) 获取匹配模式文件名函数

$(wildcard *.c) 

返回值为当前目录下所有.c 源文件列表。 

$(foreach VAR,LIST,TEXT)  循环函数  执行时把“LIST”中使用空格分割的单词依次取出赋值给变量“VAR” ,然后执行“TEXT”表达式。

dirs := a b c d 

files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))  返回每一轮值的集合,以空格分隔

        SRCDIR=../src1/ ../src2/

        SRCs=$(notdir $(foreach n,${SRCDIR},$(wildcard ${n}*.c)))

if 函数

$(call VARIABLE,PARAM,PARAM,...) call函数 将它的参数“PARAM”依次赋值给临时变量“$(1)”、“$(2)”

$(value VARIABLE)  value函数

eval函数

$(origin VARIABLE)  origin函数

shell函数   contents := $(shell cat foo)    files := $(shell echo *.c)


五、指定makefile依赖文件的搜索路径

    makefile中有2中搜索路径:依赖文件搜索路径和gcc编译文件搜索路径,前者由vpath指定,后者由-I选项指定。指定makefile依赖文件搜索路径的方法有两种:VPATH 和 vpath。 (这两种方式只能指定已经存在的文件的搜索路径,以便于判断文件更新)

VPATH:  变量“VPATH”的定义中,使用空格或者冒号(:或空格)将多个目录分开。make 搜索的目录顺序按照变量“VPATH”定义中顺序进行(当前目录永远是第一搜索目录)。

例如:

VPATH = src:../headers

它指定了两个搜索目录,“src”和“../headers”。对于规则“foo:foo.c”如果“foo.c”在“src”目录下,此时此规则等价于“foo:src:/foo.c”

vpath:当需要为不同类型的文件指定不同的搜索目录时需要这种方式它所实现的功能和“VPATH”变量很类似,但是它更为灵活。它可以为不同类型的文件(由文件名区分)指定不同的搜索目录,而且是继承性的。

它的使用方法有为:

1、vpath PATTERN DIRECTORIES   指定路径

为符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES”。多个目录使用空格或者

冒号(:)分开。类似上一小节的“VPATH” 

vpath %.c ./src   所有的.C文件在/src目录下搜索

vpath %.h ./inc   所有的.H文件在/inc目录下搜索

2、vpath PATTERN    清除之前指定的路径

清除之前为符合模式“PATTERN”的文件设置的搜索路径

vpath %.c   清除vpath %.c ./src指定的搜索路径

vpath %.h

3、vpath   清除所有指定的路径

vpath

清除所有已被设置的文件搜索路径。


在执行make命令的时候,根据makefile执行步骤,首先读入所有的makefile文件,那么

VPATH = include:src     //指定了搜索路径

或者

vpath %.h include   //指定.h类型文件的搜索路径是include

vpath %.cpp src     //指定.cpp类型文件的搜索路径是src

vpath %.cpp mpc     //指定.cpp类型文件的搜索路径是src

vpath %.cpp mvc     //指定.cpp类型文件的搜索路径是src

这仅仅是对于makefile来说搜索目标和依赖文件的路径,但是对于命令行(shell中执行的命令)来说gcc只能使用 -I 或者--incude +路径


例如依赖是:

main.o:main.cpp hello.h

g++ -c $< -Iinclude这时候,g++会自动从include目录中搜索要包含的hello.h头文件



六.makefile文件的编写

在任意目录下编写makefile文件的要点

(1)vpath指定.c文件作为依赖文件时的搜索路径

(2)模式规则+自动化变量 (多目标只能用shell for循环,而且依赖关系混乱)

(3)include 包含gcc -MM生成的.c文件的所有依赖


1.makefile文件的一些例子及其不足


 在makefile中应该将目录维护和源文件维护分开,目录维护由VPATH 指定,源文件维护指的是依赖

例子1

目录结构为

./src

  file1.cpp file2.cpp Makefile

./build                                 //build目录是最终生成可执行文件的目录

  Makefile

(1)./src/Makefile的内容

all : file1.o file2.o                   //只写依赖即可

(2)./build/Makefile的内容

VPATH = ../src                          //路径维护

include ../src/Makefile


不足:这种结构的不足 .c与.o文件同目录

在build目录下,进行make的时候,生成的中间文件就放在build目录下了。是不是很简单呢。


例子2

目录结构:

.:src head build

./src:main.c  myadd.c

./head: g.h myadd.h main.h

./build: makefile


makefile源文件:


.PHONY :clean depend   #认为目录下始终不存在的文件

tool=gcc 

vpath %.h ../head    #指定目标和依赖中用到的文件的搜索路径,不包括命令中的文件,且不包括函数参数中用到的文件

vpath %.c ../src

vpath %.o build      #指定.O文件作为依赖文件时的搜索路径,以便于判断文件更新


SRCDIR=../src/

SRCs=$(notdir $(wildcard ../src/*.c))    #将.C文件单独表示

OBJs=$(patsubst %.c,%.o,${SRCs})         #将.O文件单独表示

EXE=main


all:${EXE}

${EXE}:${OBJs} 

     ${tool} ${OBJs} -o ${EXE} -I../head

${OBJs}:%.o:%.c

     $(tool) -c $< -o $@ -I../head    #自动化变量在展开时将自动扩展.c的相对路径(否则gcc会找不到文件)

depend:  #.o默认放在当前目录下,也可以用addprefix指定路径

     ${tool} -MM $(addprefix ${SRCDIR}/,$(SRCs)) -I../head > depend

#生成所有.C文件的依赖文件到depend文件 每次make时都会执行一遍。make会自动判断,先要生成depend才能包含depend,于是必须先执行gcc命令,所以每次都会执行depend目标

clean:

     rm -f ${EXE} ${OBJs} > /dev/null     #强制删除

include depend                            #包含.C文件的依赖项


执行该makefile文件的结果:

-bash-4.1$ make

gcc   -MM ../src//main.c ../src//myadd.c -I../head > depend    #1.先生成.C文件的依赖文件

echo main.c myadd.c

main.c myadd.c

echo main.o myadd.o

main.o myadd.o

gcc   -c ../src/main.c -o main.o -I../head#2.执行自动化变量展开后的2条命令

gcc   -c ../src/myadd.c -o myadd.o -I../head

gcc   main.o myadd.o -o main -I../head#3.执行最终目标

main.o: In function `main':

main.c:(.text+0x5d): warning: the `gets' function is dangerous and should not be used.


不足:所有的.O文件与.C文件同目录


例子3:只生成.O文件的makefile文件


.PHONY :depend1 clean  #认为目录下始终不存在的文件

vpath %.c src

vpath %.o .

SRCs=$(notdir $(wildcard src/*.c))

OBJs=$(patsubst %.c,%.o,${SRCs})


all: ${OBJs}     #该目标是必须的,否则下一条多目标扩展开后,第一条规则就成了最终规则


${OBJs}:%.o:%.c

     gcc -c $< -o $@ -Ihead   # .C文件在src目录下去搜索,并判断更新;

depend1:

     gcc -MM $(addprefix src/,$(SRCs)) -Ihead >depend1

     echo ${OBJs}

     echo ${SRCs}

include depend1


2.嵌套执行make与全局变量传递(分布式makefile)


在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile(偏见),这个技术对于我们模块编译和分段编译有着非常大的好处。

      例如,有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录

下文件的编译规则。那么我们总控的Makefile可以这样书写:

   subsystem: 

           cd subdir && $(MAKE)

其等价于:

    subsystem:

           $(MAKE) -C subdir

我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量用export可以传递到下级的Makefile中,但是不会覆盖下层的Makefile中所定义的变量,而是相当于初始化了下一级的同名变量。除非指定了export -e参数 将覆盖下一级同名变量。

传递变量到下级Makefile中作为初始值: export<variable ...> 

传递所有变量:export

传递变量到下级Makefile中作为覆盖同名变量: export -e <variable ...> 

禁止传递到下级Makefile中:unexport<variable ...>

如:

      示例一:

        export variable = value    

        variable = value

        export variable

      示例二:

       export variable += value

       其等价于:

       variable += value

       export variable

     如果你要传递所有的变量,那么,只要一个export就行了。

需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行“总控Makefile”时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。

但是make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:

   subsystem:

           cd subdir && $(MAKE) MAKEFLAGS=

如果你定义了环境变量MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。

还有一个在“嵌套执行”中比较有用的参数,“-w”或是“--print-directory”会在make的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录是“/home/hchen/gnu/make”,如果我们使用“make -w”来执行,那么当进入该目录时,我们会看到:

      make: Entering directory `/home/hchen/gnu/make'.

而在完成下层make后离开目录时,我们会看到:   

   make: Leaving directory `/home/hchen/gnu/make'

当你使用“-C”参数来指定make下层Makefile时,“-w”会被自动打开的。如果参数中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”总是失效的。


分布式makefile例子:

原理:每个源文件夹都有一个自己的makefile生产相应.o都同一个目录,总控makefile负责生成main可执行文件。

例子1.

所以的makefile文件都放在build目录下

不足:无论如何每次都要重新链接main

./    下的文件有:build  head  src1   src2

bulid   下的文件有:makefile makefile1  makefile2 

head    下的文件有:  main.h myadd.h g.h

src1    下的文件有: main.c

src2    下的文件有: myadd.c


主控makefile内容:

  

main:clean     #依赖clean,使得每次都删除main重新生成,否则在.C文件改变时,可能不能执行make

make -f  makefile1   #这也是这种方法的不足,每次都要重新连接

make -f  makefile2

gcc -o main $$(echo *.o)

clean:    

rm -f main


makefile1内容:

SRCDIR=../src1/

SRCs=$(notdir $(wildcard ${SRCDIR}*.c))

OBJs=$(patsubst %.c,%.o,${SRCs})

OUTPUTDIR=./

vpath %.c ${SRCDIR}

all: ${OBJs}


${OBJs}:%.o:%.c makefile1

    gcc -c $< -o $(addprefix ${OUTPUTDIR},$@) -I../head


makefile2内容:

SRCDIR=../src2/

SRCs=$(notdir $(wildcard ${SRCDIR}*.c))

OBJs=$(patsubst %.c,%.o,${SRCs})

OUTPUTDIR=./

vpath %.c ${SRCDIR}

all: ${OBJs}


${OBJs}:%.o:%.c makefile2

    gcc -c $< -o $(addprefix ${OUTPUTDIR},$@) -I../head


例子2 makefile放在各自的源文件目录下

不足:每次所以的问价都要重新编译;

优点:每个源文件下的makefile都一样

./    下的文件有:build  head  src1   src2

bulid    下的文件有:主控makefile

head    下的文件有:  main.h myadd.h g.h

src1    下的文件有: main.c makefile

src2    下的文件有: myadd.c makefile


主控makefile内容:

  

export OUTPUTDIR=../build/   #路径传递

main:clean

    make -C ../src1

    make -C ../src2

    gcc -o main $$(echo *.o)

clean:

    rm -f main


makefile1和makefile2内容:


SRCs=$(notdir $(wildcard *.c))

OBJs=$(patsubst %.c,%.o,${SRCs})

vpath %.c .

all: ${OBJs}


${OBJs}:%.o:%.c

    gcc -c $< -o $(addprefix ${OUTPUTDIR},$@) -I../head


例子3 最好的makefile方式(自己编写的)

优点:

(1)只有一个makefile

(2)只需要维护一个源文件路径和编译-I路径

(3)能自动检测更新


目录结构:


./    下的文件有:build  head  src1   src2

bulid   下的文件有:makefile

head    下的文件有:  main.h myadd.h g.h

src1    下的文件有: main.c 

src2    下的文件有: myadd.c 


.PHONY :clean depend         #认为目录下始终不存在的文件

tool=gcc  

VPATH=../src1:../src2                #需要维护的路径(依赖文件搜索路径)

SRCDIR=$(subst :, ,${VPATH}) 

SRCs=$(notdir $(foreach n,${SRCDIR},$(wildcard ${n}/*.c)))   #foreach函数用法

OBJs=$(patsubst %.c,%.o,${SRCs})

EXE=main


all:${EXE}


${EXE}:${OBJs} 

    ${tool} ${OBJs}  -o ${EXE}  -I../head

${OBJs}:%.o:%.c

    $(tool) -c $<  -o $@  -I../head

clean:

    rm -f ${EXE} ${OBJs} > /dev/null

depend:   #所以.c的依赖生成在一个文件里面

    rm -f depend

    echo ${SRCDIR}

    for fd in ${SRCDIR};do ${tool} -MM  $$(echo $${fd}/*.c) -I../head >> depend ; done

    echo $(SRCs)

    echo $(OBJs)

clean:

    rm -f ${EXE} ${OBJs} > /dev/null

include depend


七、模式规则(目标存在%的规则)

   模式规则不同于多目标:它用来指定隐晦规则,从而生成多条规则;多目标的依赖关系比较笼统,这是它的缺点。

    模式规则的作用:在make进行规则分析时,当某些依赖文件(.o .c)没有生成规则时,make就使用该文件名在模式规则规则中匹配,如果匹配上了,就是用该模式规则来生成该文件。%.o:%.c模式规则匹配某个.o文件,依赖文件是与之同名的.c。每一个匹配上的文件都会生成一条规则。在C工程编译中,C文件应该也会送到模式规则中去检测是否存在生成.C的规则,但是始终找不到。

    模式规则重新定义隐晦规则:可以使用模式规则重新定义一个隐晦规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有"%"字符(这是必须的,以此来匹配那些没有作为目标的依赖文件)。"%"的意思是表示一个或多个任意字符。在依赖目标中同样可以使用"%",只是依赖目标中的"%"的取值,取决于其目标。

    有一点需要注意的是,"%"的展开发生在变量和函数的展开之后,变量和函数的展开发生在make载入Makefile时,而模式规则中的"%"则发生在运行时。


1、模式规则

    模式规则代表了一类规则,就像正则匹配一样,但是每一个规则的命令都是一样的。模式规则中,至少在规则的目标定义中要包含"%",否则,就是一般的规则。目标中的"%"定义表示对文件名的匹配,"%"表示长度任意的非空字符串。

    如果"%"定义在目标中,那么,目标中的"%"的值决定了依赖目标中的"%"的值,也就是说,目标中的模式的"%"决定了依赖目标中"%"的样子。例如有一个模式规则如下:

    %.o : %.c ; <command ......>

其含义是,指出了怎么从所有的[.c]文件生成相应的[.o]文件的规则。如果依赖是"a.c b.c",那么要生成的目标是"a.o b.o"。

    在mak将makefile文件读入后,make在进行规则分析时,当发现某个目标(假如为main.exe)所依赖的文件(假如为myadd.o main.o)没有生成规则或者存在生成规则但是规则中的命令为空 并且在目录下有没有找到该文件,那么make就会去查找以这些文件(假如为myadd.o main.o)为目标的隐晦规则。隐晦规则往往是一个模式规则,这样可以匹配多条规则。例如make就会依次用myadd.o main.o去匹配%.o: %.c匹配上了就依次用这条规则作为生成myadd.o main.o的规则。


2、隐晦规则中的模式规则

如果一个目标在Makefile中的所有规则都没有命令列表,make会尝试在内建的隐含规则(Implicit Rule)数据库中查找适用的规则。Make的隐含规则数据库可以用make –p命令打印,打印出来的格式也是Makefile的格式,包括很多变量和规则,其中比较常用的隐含规则是:

# default

OUTPUT_OPTION = -o $@

CC = cc

COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) –c


%.o: %.c

        $(COMPILE.c) $(OUTPUT_OPTION) $<


解释:CFLAGS这个变量没有定义,$(CFLAGS)展开是空,CPPFLAGS和TARGET_ARCH也是如此,这样$(COMPLIE.C)展开就应该是cc –c,所以%.o:%.c规则的命令:$(COMPLIE.c) $(OUTPUT_OPTION) $<展开之后就是cc –c –o $@ $<,另外$@,$<两个特殊的变量,$@的取值为规则中的目标,$<的取值为规则中的第一个条件。%.o:%.c是一种特殊的规则,称为模式规则(Pattern Rule)。回顾一下,在Makefile中,hello.o为目标的规则没有命令列表,所以make会查找隐含规则,发现隐含规则中有这样一条模式规则适用,main.o符合%.o,现在%代表main,再替换到%.c中就是main.c,随意这条模式规则相当于:

main.o: main.c

              cc  -c –o main.o main.c

.O依赖于.C的规则不必写在makefile中



3、模式的匹配


一般来说,一个目标的模式有一个有前缀或是后缀的"%",或是没有前后缀,直接就是一个"%"。因为"%"代表一个或多个字符,所以在定义好了的模式中,我们把"%"所匹配的内容叫做"茎",例如"%.c"所匹配的文件"test.c"中"test"就是"茎"。因为在目标和依赖目标中同时有"%"时,依赖目标的"茎"会传给目标,当做目标中的"茎"。


当一个模式匹配包含有斜杠(实际也不经常包含)的文件时,那么在进行模式匹配时,目录部分会首先被移开,然后进行匹配,成功后,再把目录加回去。在进行"茎"的传递时,我们需要知道这个步骤。例如有一个模式"e%t",文件"src/eat" 匹配于该模式,于是"src/a"就是其"茎",如果这个模式定义在依赖目标中,而被依赖于这个模式的目标中又有个模式"c%r",那么,目标就是"src/car"。("茎"被传递)


4、模式规则重载内建隐含规则(相当于覆盖隐晦规则)


可以重载内建的隐含规则(或是定义一个全新的),如可以重新构造和内建隐含规则不同的命令,如:

%.o : %.c

    $(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)

可以取消内建的隐含规则,只要不在后面写命令就行。如:

%.o : %.s

同样,你也可以重新定义一个全新的隐含规则,其在隐含规则中的位置取决于你在哪里写下这个规则。朝前的位置就靠前。

main:hello.o world.o

    gcc -o $@ $^

%.o:%.c  模式规则的工作方式:首先在其他所有规则的条件中去匹配%.o,如果找到(比如在main:hello.o  world.o规则中将找到hello.o和world.o),那么就生成相应的规则(生成2条:hello.o:hello.c 和world.o:world.c)

八、静态模式规则

静态模式规则和模式规则基本相同(结果相同,工作方式不同),但是静态模式规则要明确指明到哪里去匹配字符集。静态模式规则也类似于正则匹配 (所谓的模式规则就是代表多条规则)

语法:(2个冒号)

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

<commands>


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

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

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


例子1:

objects = foo.o bar.o p.m l.m

all: $(objects)


$(objects): %.o: %.c

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

上面的例子中,指明了我们的目标从$object中获取,%.o表明要所有以.o结尾的目标,也就是foo.o bar.o,而依赖模式%.c则取模式%.o的%,也就是foo bar,并为其加上.c的后缀,于是,我们的依赖目标就是foo.c bar.c。

于是这条静态模式规则在make规则分析阶段被展开成2条规则。

foo.o : foo.c

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

bar.o : bar.c

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

 

例子2:模式规则的展开(或者说匹配)

vpath %.c ../src #指定.c文件在该目录下扩展(vpath指定文件扩展和搜索路径)

OBJs=a.o b.o c.o

${OBJs}:%.o:%.c                             # 静态模式规则

    gcc -c $< -o $(addprefix build/,$@)   #自动化变量能根据vpath指定的路径将.C文件匹配到相对相对路径

以上规则将被展开成:

vpath %.c ../src         #指定.c文件在该目录下扩展(vpath指定文件扩展和搜索路径)

OBJs=a.o b.o c.o

a.o:a.c   

gcc -c $< -o ./build/a.o # 自动化变量能根据vpath指定的路径将.C文件匹配到相对相对路径

b.o:b.c

gcc -c ../src/b.c -o ./build/b.o #必须手动指定.O的生成目录,因为.O还不存在

c.o:c.c

gcc -c ../src/c.c -o ./build/c.o


八、伪目标及其重要作用

    伪目标文件的定义:伪目标就是在目录中不存在的文件,需要通过执行该规则来生成,但是作者又始终没有生成的目标文件。这样伪目标文件就是任何阶段在目录下都不存在的文件。如果目录中存在一个与伪目标同名的文件,那么这个伪目标就不是伪目标了,它虽然没有依赖但也是一个实体目标。没有依赖的实体目标是始终不会被执行的,因为make始终认为该文件时最新的;而如果目录下没有与该伪目标同名的文件,那么make总是执行该伪目标规则,企图生成伪目标文件。

伪目标的定义没有必要和实体目标区分开,它其实和实体目标是一样的。规则检测是统一的。

伪目标就是始终不会被生成的目标文件(目录下始终不存在该文件),它与实体目标没有区别。他们的规则检查与执行是完全统一的。.PHONY指明目录下没有该目标文件(即使有,也认为没有)

make规则检测:

(1)没有依赖的目标文件的规则检测

    如果目录下存在该目标文件,那么认为该目标文件时最新的,该规则永远不会被执行;如果该目标文件不存在,那么始终执行该规则,企图生成该目标文件。.PHONY关键字作用:强制认为目录下不存在该目标文件,使得make总是执行该规则,企图重新生成该目标。

(2)存在依赖的目标文件的规则检测

    如果目录下不存在该目标文件,那么make执行该规则企图生成该文件(但是未必一定要生成,取决于作者);如果存在该目标文件那么检查更新,企图生成该文件。


.PHONY强制指定一个目标为伪目标,make会认为目录下始终没有该文件。

没有用.PHONY定义的伪目标具有双重性:伪目标特性和实体目标特性。如果目录下存在该伪目标文件,那么它就是实体目标文件,否则他就是伪目标文件。根据这一特性可以实现.c文件的自动依赖和更新。

下面解释make是如何工作的:
    (1)当遇到目标体clean时,make先查看其是否有依赖体,因为clean没有依赖体,所以make认为目标体是最新的而不执行任何操作.为了编译这个目标体,必须输入make clean.
    (2)输入make clean,此时假设该目录下面不存在名为clean的文件,那么由于没有文件存在,因此make将要调用相应的规则"生成"该文件(这里只是一个形象的比喻,实际上只是执行目标clean的相应规则而已,规则中一般是不会执行生成clean文件的命令),所以使用该伪目标时,可以强制规则的执行。
    (3)然而,如果恰巧有一个名为clean的文件存在,make就会发现它.然后和前面一样,因为clean没有依赖体文件,make就认为这个文件是最新的而不会执行相关命令.为了处理这类情况,需要使用特殊的make目标体.PHONY. .PHONY的依赖体文件的含义和通常一样,但是make不检查是否存在有文件名和依赖体中的一个名字相匹配的文件,而是直接执行与之相关的命令.在使用了.PHONY之后,前面的例子如下:

伪目标的目标总是比依赖更新,这是他的特有属性。只要指定执行伪目标,那么其依赖目标将被更新,其命令将被执行。最早先的一个例子中,我们提到过一个“clean”的目标,这是一个“伪目标”,

   clean:

           rm *.o temp

并不是没有依赖的目标就一定是伪目标,一下main目标就不是伪目标。

main:

gcc -o main $$(echo *.o)   #如果main存在就不执行,不存在就执行

这种目标只检测文件是否存在,而不检测更新。

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

   .PHONY : clean

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

    .PHONY: clean

   clean:

           rm *.o temp

(1)具有依赖的伪目标。伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:

   all : prog1 prog2 prog3

   .PHONY : all

 

   prog1 : prog1.o utils.o

           cc -o prog1 prog1.o utils.o

 

   prog2 : prog2.o

           cc -o prog2 prog2.o

 

   prog3 : prog3.o sort.o utils.o

           cc -o prog3 prog3.o sort.o utils.o

我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于伪目标的特性是,总是被执行的,所以其依赖的那三个目标就总是不如“all”这个目标新。所以,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目标”。

(2)伪目标作为依赖(分布式makefile)。从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:

   .PHONY: cleanall cleanobj cleandiff

 

   cleanall : cleanobj cleandiff

           rm program

 

   cleanobj :

           rm *.o

 

   cleandiff :

           rm *.diff

“makeclean”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“makecleanall”和“make cleanobj”和“makecleandiff”命令来达到清除不同种类文件的目的



九、自动生成依赖性

    在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句“#include "defs.h"”,那么我们的依赖关系应该是:

   main.o : main.c defs.h

但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:

   cc -M main.c

其输出是:

   main.o : main.c defs.h

于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。

gcc-M main.c的输出是:

   main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \

        /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \

        /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \

        /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \

        /usr/include/bits/sched.h /usr/include/libio.h \

        /usr/include/_G_config.h /usr/include/wchar.h \

        /usr/include/bits/wchar.h /usr/include/gconv.h \

        /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \

        /usr/include/bits/stdio_lim.h

 

gcc-MM main.c的输出则是:

   main.o: main.c defs.h

那么,编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来,我们的Makefile也要根据这些源文件重新生成,让Makefile自已依赖于源文件?这个功能并不现实,不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个“name.d”的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。

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

这里,我们给出了一个模式规则来产生[.d]文件:

   %.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命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。

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

   main.o : main.c defs.h

转成:

   main.o main.d : main.c defs.h

于是,我们的[.d]文件也会自动更新了,并会自动生成了,当然,你还可以在这个[.d]文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的“include”命令,来引入别的Makefile文件(前面讲过),例如:

   sources = foo.c bar.c

   include $(sources:.c=.d)

上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d],关于这个“替换”的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次来载入文件,最先载入的[.d]文件中的目标会成为默认目标


十、make官方推荐的例子

1.make -p  查看make支持的所有隐式规则和变量

2.make -d  调试Makefile文件

3.%.o:%.c  模式规则的工作方式:首先在其他所有规则的条件中去匹配%.o,如果找到(比如在  main:hello.o  world.o规则中将找到hello.o和world.o),那么就生成相应的规则(生成2条:  hello.o:hello.c 和world.o:world.c),模式规则通常和自动变量一起使用。

4.VPATH = src1 src2 include1 include  #指定文件搜索的目录

 vpath %.c src1 src2         #分类指定文件搜索目录

 vpath %.c include1 include2 

5.include 的工作原理:当make遇到include时,直接读入文件,如果该文件不存在,则去寻找以该文件问目标的规则,试图生产该文件,然后读入该文件,如果失败则报错

6.make官方的makefile实例

CC = gcc

LD = gcc

CFLAGS = -g -W -std=c99 -c    #便于隐式规则编译生成.o文件

LDFLAGS = -lcurses -L.

SRCS = $(wildcard *.c)

INC =-L. 

all:TinyEdit


TinyEdit:main.o line.o buffer.o tools.o

    $(LD) $(LDFLAGS) $^ -o $@

myless:LDFLAGS += -lpthread

myless:myless.o line.o buffer.o

    $(LD) $(LDFLAGS) $^ -o $@

#%.o:%.c        根据不同的应用场合,可能会需要

#    $(CC) $(CFLAGS) $<

-include $(subst .c,.d,$(SRCS))  #$(SRCS)种中所有的.c用.d替代

%.d:%.c

    $(CC) -MM $(CFLAGS) $< $(INC) >$@.$$$$;\ #最后重定向

    sed 's,/($*/)/.o[ :]*,/1 $@ : ,g' < $@.$$$$ >$@;\ 

    rm -rf $@.$$$$;


解析:

$(CC) -MM $(CFLAGS) $<  命令假如对于main.c生成的结果为:

filter.o: filter.cpp comm.h thread.h sock.h web_proxy.h log.h filter_sc.h

而sed 's,/($*/)/.o[ :]*,/1 $@ : ,g' 命令的目的是添加一个.d依赖

filter.o filter.d: filter.cpp comm.h thread.h sock.h web_proxy.h log.h filter_sc.h

这样当其后的头文件改变时,filter.d也能得到更新

目前gcc 新引入了了-MT选项,所以%.d:%.c规则可以改为

%.d:%.c

    $(CC) -MM -MT "$< $@" $(CFLAGS) $< $(INC) >$@;  




本文转自 a_liujin 51CTO博客,原文链接:http://blog.51cto.com/a1liujin/1683115,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章
最新文章
相关文章