//--------------learning makefile 文件 -----------------------
1.一个简单的makefile
### makefile 开始 ###
myprog : foo.o bar.o
gcc foo.o bar.o -o myprog
foo.o : foo.c foo.h bar.h
gcc -c foo.c -o foo.o
bar.o : bar.c bar.h
gcc -c bar.c -o bar.o
### makefile 结束 ###
一个简单的Makefile文件包含一系列的“规则”,其样式如下:
目标(target)…: 依赖(prerequiries)…
<tab>命令(command)
或者
目标(target)…: 依赖(prerequiries)… ;命令(command)
<tab>命令(command)
比如上例中第一个规则,目标为myprog,它依赖foo.o 和 bar.o。
命令为gcc,它前面有一个tab符。如果跟在依赖后面,则不需要tab符。
目标的依赖也是一个目标,比如foo.c,foo.h,bar.h
2. 一定要牢记:Make的全部工作是当目标(确切地说是最终目标)需要更新时,按照您制定的具体规则执行命令。Make并不知道命令是如何工作的。
最终目标(target)是make最终努力更新的目标。其它更新的目标是因为它们作为最终目标的依赖,或依赖的依赖,等等以此类推。如果一些规则和最终目标无任何关联则这些规则不会被执行。
缺省的最终目标是第一个规则的第一个目标。例外:以句点(‘.’)开始的目标不是缺省最终目标
可以在make命令中指定最终目标 —— make foo.o
3. 执行makefile文件
1.缺省情况下,当make寻找makefile文件时,它试图搜寻具有如下的名字的文件,按顺序:‘GNUmakefile’、‘makefile’和‘Makefile’。
2.用参数指定makefile文件:make -f file1 file2 file3
可以指定多个文件,将按次序一个一个执行。
4. include :包含其他makefile文件
include filename1 filename2 filename3
include不是一个命令,不能以tab键开头。
include把其他文件包含进来。
也可以用参数指定包含哪些文件:make -include
如果makefile文件名不以‘/’开头,并且在当前目录下也不能找到,则需搜寻另外的目录。
首先,搜寻以‘-|’或‘--include-dir’参数指定的目录,然后依次搜寻下面的目录(如果它们存在的话):‘prefix/include' (通常为 ‘/usr/local/include') ‘/usr/gnu/include', ‘/usr/local/include', ‘/usr/include'。
5. $$ —— 为美圆符
* 为通配符, /* 为星号,显然 / 将特殊符号恢复本义。
在定义变量时,通配符为本义,比如:
objs = *.o 定义objs为名字为*的.o文件,而不是所有的.o文件。
objs = ${wildcard *.o} 定义objs为所有的.o文件。
在规则中通配符会扩展,但如果扩展时,找不到匹配的文件,则通配符恢复本义,比如:
myprog : *.o
如果没有一个.o文件,则myprog会依赖文件*.o文件。
6. 在目录中搜寻依赖
VPATH变量
如果一个作为目标或依赖的文件在当前目录中不存在,make就会在VPATH指定的目录中搜寻该文件。
如果在这些目录中找到要寻找的文件,则就象这些文件在当前目录下存在一样,规则把这些文件指定为依赖。
在VPATH变量定义中,目录的名字由冒号或空格分开。
vpath指令
vpath pattern directories
对一定格式类型的文件名指定一个搜寻路径。搜寻的路径由一列要搜寻的目录构成,目录由冒号(在MS-DOS、MS-WINDOWS系统中用分号)或空格隔开,和VPATH变量定义要搜寻的路径格式一样。
vpath pattern
清除和一定类型格式相联系的搜寻路径。
vpath
清除所有前面由vapth指令指定的搜寻路径。
当对同一个pattern定义多个dir时,会顺序搜索这些dir,比如
vpath %.c foo
vpath % blish
vpath %.c bar
表示搜寻`.c'文件先搜寻目录`foo'、然后`blish',最后`bar';如果是如下代码:
vpath %.c foo:bar
vpath % blish
表示搜寻`.c'文件先搜寻目录‘foo'、然后‘bar',最后‘blish'。
目录搜寻过程
当通过目录搜寻找到一个文件,该文件有可能不是您在依赖列表中所列出的依赖;有时通过目录搜寻找到的路径也可能被废弃。Make决定对通过目录搜寻找到的路径保存或废弃所依据的算法如下:
1、如果一个目标文件在makefile文件所在的目录下不存在,则将会执行目录搜寻。
2、如果目录搜寻成功,则路径和所得到的文件暂时作为目标文件储存。
3、所有该目标的依赖用相同的方法考察。
4、把依赖处理完成后,该目标可能需要或不需要重新创建:
1、如果该目标不需要重建,目录搜寻时所得到的文件的路径用作该目标所有依赖的路径,同时包含该目标文件。简而言之,如果make不必重建目标,则您使用通过目录搜寻得到的路径。(?不懂!)
2、如果该目标需要重建,目录搜寻时所得到的文件的路径将废弃,目标文件在makefile文件所在的目录下重建。简而言之,如果make要重建目标,是在makefile文件所在的目录下重建目标,而不是在目录搜寻时所得到的文件的路径下。(?really?)
该算法似乎比较复杂,但它却可十分精确的解释实际您所要的东西。
其它版本的make使用一种比较简单的算法:如果目标文件在当前目录下不存在,而它通过目录搜寻得到,不论该目标是否需要重建,始终使用通过目录搜寻得到的路径。
建议:如果目标不与makefile同目录,则应该指定它的路径(?好建议?)
连接库的搜寻目录
当一个依赖的名字是‘-|name’的形式时,make特别地在当前目录下、与vpath匹配的目录下、VPATH指定的目录下以及‘/lib’, ‘/usr/lib', 和 ‘prefix/lib'(正常情况为`/usr/local/lib',但是MS-DOS、MS-Windows版本的make的行为好像是prefix定义为DJGPP安装树的根目录的情况)目录下搜寻名字为‘libname.so'的文件然后再处理它。
如果没有搜寻到‘libname.so'文件,那么在前述的目录下搜寻‘libname.a'文件。
例如,如果在您的系统中有‘/usr/lib/libcurses.a'的库文件,则:
foo : foo.c -lcurses
cc $^ -o $@
如果‘foo’比‘foo.c’更旧,将导致命令‘cc foo.c /usr/lib/libcurses.a -o foo'执行。
实际上,从'-|name'到'libname.so'或'libname.a'的转换是由 .LIBPATTERNS变量支持的。
.LIBPATTERNS变量的缺省值是"‘lib%.so lib%.a'",可以重定义该变量,比如为空值。
3. 隐含规则:如果一个目标没有对应的规则(即在规则中,它是目标而不是目标的依赖),或者规则中没有命令, 就会启动隐含规则。
如果隐含规则在最终目标的依赖链中,那么它总被执行。
依后缀名的隐含规则:
Compiling C programs(编译C程序)
'n.o' 自动由'n.c' 使用命令 '$(CC) -c $(CPPFLAGS) $(CFLAGS)'生成 。 #即如果目标名符合“n.o”,那么此隐含规则就适用
Compiling C++ programs (编译C++程序)
'n.o'自动由'n.cc' 或'n.C'使用命令'$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)'生成。 我们鼓励您对C++源文件使用后缀'.cc' 代替后缀'.C'。
Assembling and preprocessing assembler programs (汇编以及预处理汇编程序)
'n.o'自'n.S'运行C编译器,cpp,生成。命令为:'$(CPP) $(CPPFLAGS)'。
Linking a single object file (连接一个简单的OBJ文件)
'n' 自动由'n.o' 运行C编译器中的连接程序 linker (通常称为 ld)生成。命令为: '$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)'
也可以连接多个obj文件,但要求目标与一个依赖目标同名,不过后缀可不同,通常省略同名的依赖目标:如
x: y.o z.o
此时:1.省略了一个依赖目标:x.o, 还原后为:x: x.o, y.o, z.o. ?如果x.o不存在,将不会适用该隐含规则吗?
2.隐含规则为$(CC) $(LDFLAGS) x.o y.o z.o $(LOADLIBES) $(LDLIBS) -o x
Yacc for C programs (由Yacc生成C程序)
'n.c'自动由'n.y'使用命令'$(YACC) $(YFLAGS)'运行 Yacc生成。
Lex for C programs (由Lex生成C程序)
'n.c'自动由'n.l' 运行 Lex生成。命令为:'$(LEX) $(LFLAGS)'。
隐含规则中使用的变量,包括命令名变量和命令使用的参数变量。下面是命令名变量:
CC
C语言编译程序;缺省为:'cc'.
CXX
C++编译程序;缺省为:'g++'.
CPP
带有标准输出的C语言预处理程序;缺省为:'$(CC) -E'.
YACC
将 Yacc语言转变为 C程序的程序;缺省为:'yacc'.
MAKEINFO
将Texinfo 源文件转换为信息文件的程序;缺省为:'makeinfo'.
RM
删除文件的命令;缺省为:'rm -f'.
这里是值为上述命令的附加参数的变量列表。在没有注明的情况下,所有变量的值缺省为空值。
CFLAGS
用于C编译器的额外标志。
CXXFLAGS
用于C++编译器的额外标志。
CPPFLAGS
用于C预处理以及使用它的程序的额外标志 (C和 Fortran 编译器)。
YFLAGS
用于Yacc的额外标志。Yacc。
隐含规则链:启动隐含规则时,如果隐含规则的依赖目标(可能是个文件)不存在,则会启动一个新的隐含规则。依次类推,会形成一个隐含规则链。
比如:从文件myprog.y到文件myprog.o,要首先运行隐含规则Yacc,其次运行隐含规则cc。
注意:运行隐含规则链时会生成中间文件(但是,如果其中有文件在makefile中被提及,则它不是中间文件),如果这些中间文件存在,则会更新这些文件,如果不存在,会在更新最终目标后删除这些中间文件。
.SECONDARY : 中间文件列表
.SECONDARY 目标告诉make不要删除列出的中间文件
.INTERMEDIATE : 中间文件列表
.INTERMEDIATE 目标告诉make列出的是中间文件,即使这些文件可能在makefile中提及。
没有一条隐含规则可以在隐含规则链中出现两次以上(含两次)。这可以强制make在搜寻一个隐含规则链时阻止无限循环。
可以把隐含规则链看作是一个隐含规则。
对同一个后缀可能会有多个隐含规则适用。则这些规则会按优先顺序执行其中一条。比如:
从文件myprog.c到执行文件myprog,可以有规则链编译——连接,也可以直接用命令cc同时编译和连接。后一条规则优先。
2.依靠dependent
myprog : foo.o bar.o
表示目标myprog依靠foo.o和bar.o两个目标(target).“依靠”的意思是说:文件myprog的时间戳应该比文件foo.o或文件bar.o新,如果比它们旧的话,就需要运行
目标myprog,即允许gcc foo.o bar.o -o myprog.
依靠是有连锁反应的,目标foo.o又依靠foo.c ,foo.h, bar.h, 如果这三个文件中有一个比foo.o新,就允许目标foo.o
一般,要保证目标文件和源程序(.c和所有包含的.h)同步。
(使用 gcc 的时候,用 -M 开关,它会为每一个你给它的C文件输出一个规则,把目标文件 做为目的,而这个C文件和所有应该被 #include 的 header 文 件将做为依靠文件。注意这个规则会加入所有 header 文件,包 括被角括号(`<', `>')和双引号(`"')所包围的文件。其实我们可以 相当肯定系统 header 档(比如 stdio.h, stdlib.h 等等)不会 被我们更改,如果你用 -MM 来代替 -M 传递给 gcc,那些用角括 号包围的 header 档将不会被包括。(这会节省一些编译时间))
5。假象目标(Phony Targets)
1、一个目标就是一个文件,一个目标“依靠”另外几个目标,就是保证这个文件比依靠的文件要新,如果旧的话,就执行该目标下的命令。
2、如果一个目标myprog依靠另一个目标foo.o,目标foo.o又依靠目标foo.c, 那么依次序foo.c —— foo.o——myprog执行。
3、如果一个目标(实际上是文件名)不存在,那么它总是最旧的。比如:
all : foo.o bar.o
gcc foo.o bar.o -o myprog
由于文件“all”总不存在,所以命令gcc ...一旦在最终目标的依赖链中就会被执行。
再比如,
cleanall :
rm *.o
rm myprog
cleanall并不存在,但它没有依赖,所以它也总是最旧的,一旦在最终目标的依赖链中就会被执行。
cleanall会删除所有.o和myprog文件。
如果一个依靠目标不存在,那么是不是和这种情况一样?
4、如果一个目标没有依赖,那么它总是最新的。(当然,如果目标自己也不存在,根据第⒊条,它总是最旧的。)
比如:
foo.o:
gcc -o foo.o foo.c
foo.o存在,所以gcc命令总不会被执行。
再比如:在上例中,文件cleanall存在,那么命令make cleanall 也不会运行规则的命令,因为目标已经是最新的。
显然,这种情况要避免。解决办法是:
使用目标不存在、且没有命令和依赖的规则:
clean: FORCE
rm $(objects)
FORCE:
make会认为FORCE规则总要被执行,执行结果是FORCE是最新的,目标clean将比它旧,所以clean会被执行。
简单地说:空依赖是最旧的,FORCE依赖是最新的。这两点可以利用。
另一种解决办法是使用.PHONY目标,见下面第5.条。
5、内建的假象目标“.PHONY”: 系统知道它是一个不存在的文件,所以根本不去检查磁盘或查找隐含规则。
一个.PHONY目标依赖的目标也将是假象目标。
比如:
.PHONY : cleanall
即使cleanall文件存在,目标cleanall也是个假象目标。实际上,make根本不去检查cleanall存不存在。
一般,一个假想目标不应该是一个实际目标文件的依赖。
6. /
一行命令写在两行时,用“/”连接
如:
rm edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
多行注释也可以用/
如:# line1 /
line2
3.Makefile 变量
例子:
### makefile 开始 ###
OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g
myprog : $(OBJS)
$(CC) $(OBJS) -o myprog
foo.o : foo.c foo.h bar.h
$(CC) $(CFLAGS) -c foo.c -o foo.o
bar.o : bar.c bar.h
$(CC) $(CFLAGS) -c bar.c -o bar.o
### makefile 结束 ###
= 比如CC = gcc, 要设定一个变量,你只要在一行的开始写下这个变量的名字,后面跟一个 = 号,后面跟你要设定的这个变量的值。
:= 比如CC := gcc,“:=”表示立即赋值, 一般情况下与“=”是一样的。下面情况会有区别:
CC = gcc
CC2 = ${CC}
CC = cc
这三句执行后,变量CC2的值是cc, 而不是gcc. 就好象CC2与表达式${CC}绑定,在需要它时,就计算
再看:
CC = gcc
CC2 := ${CC}
CC = cc
这三句执行后,变量CC2的值是gcc, 而不是cc. 这里CC2的值是立即计算,并不随变量CC的改变而改变。
以后你要引用 这个变量,写一个 $ 符号,后面是围在括号里的变量名。
三个比较有用的build_in变量是 $@, $< 和 $^ (这些变量不需要括号括住)。
$@ 扩展成当前规则的目的文件名(即当前的target),
$< 扩展成依靠列表中的第一个依靠文件,
$^ 扩展成整个依靠的列表(属于当前target的整个依靠列表,除掉了里面所有重复的文件名)。
利用$@, $< , $^,重写上面的例子:
### makefile 开始 ###
OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g
myprog : $(OBJS)
$(CC) $^ -o $@
foo.o : foo.c foo.h bar.h
$(CC) $(CFLAGS) -c $< -o $@
bar.o : bar.c bar.h
$(CC) $(CFLAGS) -c $< -o $@
### makefile 结束 ###
解释:
目标myprog中,$^ = $(OBJS) = foo.o bar.o
$@ = myprog
目标foo.o中, $< = foo.c
$@ = foo.o
目标bar.o中, $< = bar.c
$@ = bar.o
显然,使用内建变量$@, @<, @^可以减少修改的位置。
4。 自动变量是内建变量,其名字为“变量符$ + 名字”,比如$@
自动变量列表:
$@
规则的目标文件名。如果目标是一个档案成员,则变量'$@' 档案文件的文件名。对于有多个目标的格式规则(参阅格式规则简介),变量'$@'是那个导致规则命令运行的目标文件名。
$%
当目标是档案成员时,该变量是目标成员名,参阅使用make更新档案文件。例如,如果目标是'foo.a(bar.o)',则'$%'的值是'bar.o','$@'的值是'foo.a'。如果目标不是档案成员,则'$%'是空值。
$<
第一个依赖的文件名。如果目标更新命令来源于隐含规则,该变量的值是隐含规则添加的第一个依赖。参阅使用隐含规则。
$?
所有比目标'新'的依赖名,名字之间用空格隔开。对于为档案成员的依赖,只能使用已命名的成员。参阅使用make更新档案文件。
$^
所有依赖的名字,名字之间用空格隔开。对于为档案成员的依赖,只能使用已命名的成员。参阅使用make更新档案文件。对同一个目标来说,一个文件只能作为一个依赖,不管该文件的文件名在依赖列表中出现多少次。所以,如果在依赖列表中,同一个文件名出现多次,变量'$^'的值仍然仅包含该文件名一次。
$+
该变量象'$^',但是,超过一次列出的依赖将按照它们在makefile文件中出现的次序复制。这主要的用途是对于在按照特定顺序重复库文件名很有意义的地方使用连接命令。
$*
和隐含规则匹配的stem(径),参阅格式匹配。如果一个目标为'dir/a.foo.b',目标格式规则为:'a.%.b' ,则stem为'dir/foo'。在构建相关文件名时stem 十分有用。
在静态格式规则中,stem是匹配目标格式中字符'%'的文件名中那一部分。在一个没有stem具体规则中;变量'$*' 不能以该方法设置。
如果目标名以一种推荐的后缀结尾(参阅过时的后缀规则),变量'$*'设置为目标去掉该后缀后的部分。例如,如果目标名是'foo.c',则变量'$*' 设置为'foo', 因为'.c' 是一个后缀。GNU make 处理这样奇怪的事情是为了和其它版本的make兼容。
在隐含规则和静态格式规则以外,您应该尽量避免使用变量'$*'。在具体规则中如果目标名不以推荐的后缀结尾,则变量'$*'在该规则中设置为空值。
当您希望仅仅操作那些改变的依赖,变量'$?' 即使在具体的规则中也很有用。例如,假设名为'lib'的档案文件包含几个OBJ文件的拷贝,则下面的规则仅将发生变化的OBJ文件拷贝到档案文件:
lib: foo.o bar.o lose.o win.o
ar r lib $?
在上面列举的变量中,有四个变量的值是单个文件名。三个变量的值是文件名列表。这七个变量都有仅仅存放文件的路径名或仅仅存放目录下文件名的变体。
变量的变体名是由变量名追加字母'D'或'F'构成。这些变体在GNU make中处于半废状态,原因是使用函数T dir和notdir 能够得到相同的结果。
参阅文件名函数。注意,'F'变体省略所有在dir函数中总是输出的结尾斜杠这里是这些变体的列表:
`$(@D)'
目标文件名中的路径部分,结尾斜杠已经移走。如果变量`$@'的值是`dir/foo.o',变体 `$(@D)'的值是`dir'。 如果变量`$@'的值不包含斜杠,则变体的值是`.'。
`$(@F)'
目标文件名中的真正文件名部分。如果变量`$@'的值是`dir/foo.o',变体 `$(@F)'的值是` foo.o '。`$(@F)' 等同于 `$(notdir $@)'。
`$(*D)'
`$(*F)'
stem(径)中的路径名和文件名;在这个例子中它们的值分别为:`dir' 和 `foo' 。
`$(%D)'
`$(%F)'
档案成员名中的路径名和文件名;这仅对采用'archive(member)'形式的档案成员目标有意义,并且当成员包含路径名时才有用。参阅档案成员目标。
`$(<D)'
`$(<F)'
第一个依赖名中的路径名和文件名。
`$(^D)'
`$(^F)'
所有依赖名中的路径名和文件名列表。
`$(?D)'
`$(?F)'
所有比目标'新'的依赖名中的路径名和文件名列表。
注意,在我们讨论自动变量时,我们使用了特殊格式的惯例;我们写"the value of'$<'", 而不是"the variable <" ;和我们写普通变量,例如变量 objects 和 CFLAGS一样。我们认为这种惯例在这种情况下看起来更加自然。这并没有其它意义,变量'$<'的变量名为 < 和变量'$(CFLAGS)' 实际变量名为CFLAGS一样。您也可以使用'$(<)'代替'$<'。
5. 隐含规则搜寻算法
这里是make为一个目标't'搜寻隐含规则的过程。这个过程用于任何没有命令的双冒号规则,用于任何不含命令的普通规则的目标,以及用于任何不是其它规则目标的依赖。这个过程也能用于来自隐含规则的依赖递归调用该过程搜寻规则链。
在本算法中不提及任何后缀规则,因为后缀规则在makefile文件读入时转化为了格式规则。
对于个是'archive(member)'的档案成员目标,下述算法重复两次,第一次使用整个目标名't',如果第一次运行没有发现规则,则第二次使用'(member)'作为目标't'。
1、在't'中分离出路径部分,称为'd',剩下部分称为'n'。例如如果't'是'src/foo.o',那么'd'是'src/';'n'是'foo.o'。
2、建立所有目标名匹配't'和'n'的格式规则列表。如果目标格式中含有斜杠,则匹配't',否则,匹配'n'。
3、如果列表中有一个规则不是万用规则,则从列表中删除所有非最终万用规则。
4、将没有命令的规则也从列表中移走。
5、对每个列表中的格式规则:
1、寻找stem's',也就是和目标格式中%匹配的't'或'n'部分。
2、使用stem's'计算依赖名。如果目标格式不包含斜杠,则将'd'添加在每个依赖的前面。
3、测试所有的依赖是否存在或能够创建。(如果任何文件在makefile中作为目标或依赖被提及,则我们说它应该存在。)如果所有依赖存在或能够创建,或没有依赖,则可使用该规则。
6、如果到现在还没有发现能使用的规则,进一步试。对每一个列表中的规则:
1、 如果规则是最终规则,则忽略它,继续下一条规则。
2、 象上述一样计算依赖名。
3、 测试所有的依赖是否存在或能够创建。
4、 对于不存在的依赖,按照该算法递归调用查找是否能够采用隐含规则创建。
5、 如果所有依赖存在或能使用隐含规则创建,则应用该规则。
7、 如果没有隐含规则,则如有用于目标'.DEFAULT'规则,则应用该规则。在这种情况下,将目标'.DEFAULT'的命令给与't'。
一旦找到可以应用的规则,对每一个匹配的目标格式(无论是't'或'n')使用stem's'替换%,将得到的文件名储存起来直到执行命令更新目标文件't'。在这些命令执行以后,把每一个储存的文件名放入数据库,并且标志已经更新,其时间戳和目标文件't'一样。
如果格式规则的命令为创建't'执行,自动变量将设置为相应的目标和依赖(参阅自动变量)。
4。隐含规则 (Implicit Rules)
当一个目标下没有命令时,会调用隐含命令。
比如foo.o目标下没有命令gcc foo.c foo.h bar.h -o foo.o时会调用隐含命令:
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
翻译后为:
gcc $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c foo.c -o foo.o
变量$(CFLAGS) ,$(CPPFLAGS) ,$(TARGET_ARCH)需自己定义,用来控制编译过程。
?使用哪一种隐含规则由什么决定。
6.函数 (Functions)
不会在makefile中定义一个函数,而只会引用一个函数: ${函数名 空格 参数1,参数2,参数三,...}。
引用一个函数,可以将其运行结果赋给一个变量,比如:
SOURCES = $(wildcard *.c)
函数wildcard将所有匹配“*.c”的文件的文件名列出,结果赋给变量SOURCES
再比如:
OBJS = $(patsubst %.c,%.o,$(SOURCES))
函数patsubst在变量${SOURCES}中匹配“%.c”字符串,将字符串“%.c”替换成“%.o”。简单地说,就是将${SOURCES}中的“c”替换为“o”
通配符%表示一个或多个字符;
通配符*表示零个或多个字符;
7.规则(rule)
target: components
TAB rule
每个rule的前面要有一个tab符
7.一个复杂的例子:
(注意:在一行的最后并不能跟注释,下面这样写只是为了方便讲解,实际用时要删除这些注释)
### makefile 开始 ###
######################################
#
# Generic makefile
#
# by George Foot
# email: george.foot@merton.ox.ac.uk
######################################
### Customising
# 用户设定
#
# Adjust the following if necessary; EXECUTABLE is the target
# executable's filename, and LIBS is a list of libraries to link in
# (e.g. alleg, stdcx, iostr, etc). You can override these on make's
# command line of course, if you prefer to do it that way.
#
# 如果需要,调整下面的东西。 EXECUTABLE 是目标的可执行文件名, LIBS
# 是一个需要连接的程序包列表(例如 alleg, stdcx, iostr 等等)。当然你
# 可以在 make 的命令行覆盖它们,你愿意就没问题。
#
EXECUTABLE := mushroom.exe
LIBS := alleg
# Now alter any implicit rules' variables if you like, e.g.:
#
# 现在来改变任何你想改动的隐含规则中的变量,例如
CFLAGS := -g -Wall -O3 -m486 #隐含规则会用到这些变量
CXXFLAGS := $(CFLAGS)
# The next bit checks to see whether rm is in your djgpp bin
# directory; if not it uses del instead, but this can cause (harmless)
# `File not found' error messages. If you are not using DOS at all,
# set the variable to something which will unquestioningly remove
# files.
#
# 下面先检查你的 djgpp 命令目录下有没有 rm 命令,如果没有,我们使用
# del 命令来代替,但有可能给我们 'File not found' 这个错误信息,这没
# 什么大碍。如果你不是用 DOS ,把它设定成一个删文件而不废话的命令。
# (其实这一步在 UNIX 类的系统上是多余的,只是方便 DOS 用户。 UNIX
# 用户可以删除这5行命令。)
ifneq ($(wildcard $(DJDIR)/bin/rm.exe),) #查找${DJDIR)/bin/rm.exe文件,(这里通过与空串比较看是否找到)
RM-F := rm -f #定义变量RM-F 为 rm -f
else
RM-F := del
endif
# You shouldn't need to change anything below this point.
#
# 从这里开始,你应该不需要改动任何东西。(我是不太相信,太NB了!)
SOURCE := $(wildcard *.c) $(wildcard *.cc) #查找所有.c 和 .cc的源文件列表
OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE))) #生成目标文件列表,注意:相同名字的.c和.cc文件将生成重复的.o文件
DEPS := $(patsubst %.o,%.d,$(OBJS)) #生成.d 结尾的DEPS文件列表
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS)) #分两步:1.wildcard ${DEPS} 找出所有已存在的.d文件 ;
# 2.filter-out将找出的.d文件从DEPS文件列表中删除,剩余是不存在的.d文件列表
#不存在的DEPS文件列表放在变量MISSING_DEPS中。
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) / #分两步:1.patsubst %.d, %.c, ${MISSING_DEPS}列出所有.d文件对应的.c源文件,后面的一个patsubst也一样
$(patsubst %.d,%.cc,$(MISSING_DEPS))) # 2.wildcard 源文件列表c 源文件列表cc 找出源文件列表c和源文件列表cc存在的源文件
#为什么用patsubst后还要用wildcard呢?因为一个.d文件可能对应一个.c文件,也可能对应一个.cc文件,所以这里查找以找到实际的.c或.cc文件
CPPFLAGS += -MD #?+= 与自赋值运算符很像
.PHONY : everything deps objs clean veryclean rebuild
everything : $(EXECUTABLE)
deps : $(DEPS) #目标deps依靠DEPS文件列表
#会触发隐含的命令吗?
objs : $(OBJS) #目标objs依靠OBJS文件列表
#会触发隐含的命令吗?
clean : #一个假像目标,没有依靠文件,不会自动运行
@$(RM-F) *.o #删除所有.o文件
@$(RM-F) *.d #删除所有.d文件
veryclean: clean #一个假象目标,依靠假象目标clean
@$(RM-F) $(EXECUTABLE) #删除生成的可执行文件
rebuild: veryclean everything #一个假象目标,依靠veryclean和everything
ifneq ($(MISSING_DEPS),) #如果有不存在的DEPS文件(即.d文件)
$(MISSING_DEPS) : #目标是所有不存在的DEPS文件,没有依靠的文件
@$(RM-F) $(patsubst %.d,%.o,$@)
endif
-include $(DEPS) #将DEPS文件包含到makefile中,与c语言的#include很像,为纯文本替换。
$(EXECUTABLE) : $(OBJS) #可执行文件,依靠所有.o文件
gcc -o $(EXECUTABLE) $(OBJS) $(addprefix -l,$(LIBS)) #分两步:1.addprefix -l,${LIBS} 将${LIBS}中的每一项加上前缀“-l”(不是数字1)
# 2.命令gcc,根据.o文件和库文件生成可执行文件
### makefile 结束 ###
append:
Administrator@XTY-FE540439EF0 /cygdrive/d/cygwin/xty/src
$
1. $ gcc a.c -o a.o
生成a.o
$ gcc a.c -o a
生成a.exe
$ gcc a.c
生成a.exe
$ gcc a.o
a.o(.text+0x0):libgcc2.c: multiple definition of `mainCRTStartup'
/usr/lib/crt0.o(.text+0x0): first defined here
a.o(.data+0x0):/cygnus/netrel/src/gcc-2.95.3-5/gcc/libgcc2.c: multip
n of `__cygwin_crt0_bp'
/usr/lib/crt0.o(.data+0x0): first defined here
collect2: ld returned 1 exit status
$ gcc a.o -nostartfiles
生成a.exe
注:当用gcc编译源程序为“.o”文件的时候,需要加一个“-nostartfiles”选项。这个选项使得C编译器不链接系统的启动函数库里面的启动函数。否则,就会得到一个“multiple-definition”的错误。
$ gcc a.c -o a.x
生成a.x
$ gcc a.x -nostartfiles
生成a.exe
注:生成的exe运行后不打印“hello world”。(是不是目标文件的后缀.x造成的?)