首先要把源文件编译成中间代码文件,在Windows下也就是
目标 : 依赖的条件 (注意冒号两边有空格)
命令 (注意前面用tab键开头)
正如前面所说的,如果一个工程有3个头文件,和8个C文件,我们为了完成前面所述的那三个规则,我们的Makefile应该是下面的这个样子的。
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
cc -o edit
//-o 指定输出文件名
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
我们可以把这个内容保存在文件为“Makefile”或“makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。 或者自己随便起名字,比如Makefile111,
使用变量:
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
。。。。。
。。。。。
utils.o : utils.c defs.h
cc -c utils.c
.PHONY : clean
clean :
-rm edit $(objects)
GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,也就是说上面的
cc -c main.c
。。
cc -c utils.c
都可以省去。
每个Makefile 中都应该写一个清空目标文件(.o 和执行文件)的规则,
.PHONY意思表示clean是一个“伪目标”。 我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签。
“.PHONY”来显示地指明一个目标是“伪目标”
“clean从来都是放在文件的最后”。
伪目标同样可以作为“默认目标”。一个示例就是,如果你的Makefile 需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile 中,那么你可以使用“伪目标”这个特性。
Makefile 中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它2个目标。伪目标的特性是,总是被执行的。
所以,其它2个目标的规则总是会被执行。
all : prog1 prog2
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。在Makefile 的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。
在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的
源文件分类,并存放在不同的目录中。所以,当make需要去找寻文
件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个
路径告诉make,让make在自动去找。
vpath是关键字
vpath
为符合模式的文件指定搜索目录。
vapth使用方法中的需要包含“%”字符。“%”的意思是匹配零
或若干字符。
vpath %.h ../headers
该语句表示,要求make在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
多目标
刚刚我们用 伪目标的方法实现了一种多目标的生成方法。
当多个目标的生成规则的执行命令是同一个,这可能会可我们带来麻烦,不过好在我们的可以使用一个自动化变量“$@”,这个变量表示着目前规则中所有的目标的集合。
静态模式可以更加容易地定义多目标的规则
: :
...
看一个例子
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c
上面的例子中,指明了我们的目标all依赖于$object,
对于
“%.o”(就是上面的
target-pattern
)表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo
令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也
就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)。
于是,上面的规则展开后等价于下面的规则:
foo.o : foo.c
$(CC) -c
bar.o : bar.c
$(CC) -c
自动生成依赖性
在Makefile 中,我们的依赖关系可能会需要包含一系列的头文件,比
如,如果我们的main.c 中有一句“#include "defs.h"”,那么我们的依赖
关系应该是:
main.o : main.c defs.h
但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile。
大多数C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:
cc -M main.c
就行了
如果你使用GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”
参数会把一些标准库的头文件也包含进来。
书写规则:
make 会把其要执行的命令行在命令执行前输出到屏幕上。当
我们用“@”字符在命令行前,那么,这个命令将不被make显示出来。
命令: @echo 正在编译XXX模块......
显示:
命令:
显示: echo 正在编译XXX模块......
如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命
令写在一行上,用分号分隔。比如这样才能打印当前路径
exec:
cd /home/hchen; pwd
pwd会打印出“/home/hchen”。
make中的另一种用变量来
定义变量的方法。这种方法使用的是“:=”操作符(其实就相当于perl里面的连字符),如:
x := foo
y := $(x) bar
x := later
其等价于:
y := foo bar
x := later
值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使
用前面已定义好了的变量。
还有一个比较有用的操作符是“?=”,先看示例:
FOO ?= bar
其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,
如果FOO先前被定义过,那么这条语将什么也不做。
还有一个比较有用的操作符是“?=”,先看示例:
FOO ?= bar
其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,
如果FOO先前被定义过,那么这条语将什么也不做,其等价于:
变量值的替换
我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是
“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。
foo := a.o b.o c.o
bar := $(foo:.o=.c)
这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把
“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”
的值就是“a.c b.c c.c”。
Makefile 文件里面 用 :=,表示变量赋值的时候立刻展开。 用 =,表示变量被用的时候才展开。
B :=$(A)时,它只会到这句语句之前去找A的值,因A没有定义所以什么都没有输出。
B = $(A)时,虽然该语句之前A没有定义,但是在其后定义了,用后面定义的。
下面是例子: animal=frog #输出结果是: #输出结果是:
第二种高级用法是——“把变量的值再当成变量”。先看一个例子:
x = y
y = z
a := $($(x))
在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就
是“z”。(注意,是“x=y”,而不是“x=$(y)”)
我们可以使用“+=”操作符给变量追加值,如:
objects = main.o foo.o bar.o utils.o
objects += another.o
于是,我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”
还有一种设置变量值的方法是使用define关键字。使用define关键字
设置变量的值可以有换行,这有利于定义一系列的命令
因为命令需要以[Tab]
键开头,所以如果你用define定义的命令变量中没有以[Tab]键开头,
那么make就不会把其认为是命令。
下面的这个示例展示了define的用法:
define two-lines
echo foo
echo $(bar)
endef
使用条件判断
下面的例子,判断$(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
三个关键字:ifeq、else 和endif。
类似的还有 “ifneq”“ifdef”“ifndef”
注意这些不是命令。不
能以[Tab]键做为开始(不然就被认为是命令),但可以有空格。
你最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。
函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:
$( )
或是
${ }
示例:
$(subst ee,EE, feet on thestreet),
把“feet
名称:去空格函数— strip。
功能:去掉字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。
示例:
$(strip a b c
把字串“a b c
过滤函数— filter。
$(filter , )
示例:
sources
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o
$(filter %.c %.s,$(sources))返回的值是“foo.c bar .c baz.s”
$(dir )
名称:取目录函数— dir。
功能:从文件名序列中取出目录部分。目录部分是指最后一
个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。
返回:返回文件名序列的目录部分。
示例:
$(notdir )
功能:从文件名序列中取出非目录部分。非目录部分是指最
后一个反斜杠(“/”)之后的部分。
names
files := $(foreach
上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,
“$(n).o”每次根据“$(n)”计算出一个值, 这些值以空格分隔, 最后作为
foreach 函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。
来编译。-I.是指gcc在当前目录(.)下寻找include文件。 CC = gcc
CFLAGS = -I.
DEPS = calc.h
OBJ = main.o getch.o getop.o stack.o
%.o : %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
calc: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
|
%表示匹配0个或多个字符。例如 %.h”表示所有以“.h”结尾的文件。
首先宏定义DEPS,声明.c文件所依赖的.h文件。然后我们定义一条规则,为所有的.c文件生成一个.o文件。规则描述:.o文件依赖于.c文件和DEPS中声明的.h文件,为了产生.o文件,make需要使用CC中声明的编译器来编译.c文件。
-c 意味着产生object文件,-o $@ 意思是编译生成的文件用上面的%.o来命名,$< 指依赖关系中的第一项(%.c)CFLAGS的定义和之前一样。最后为了简化,我们使用特殊的宏定义 $@ 和 $^ ,分别表示冒号(:)的左右两边。为了让make中所有的规则更具通用性,在Version 4中,我们把所有的include文件作为DEPS的一部分,所有的object文件作为OBJ的一部分: