makefile 结构
基本 makefile 结构
GNU Make 的主要工作是读进一个文本文件, makefile 。这个文件里主要是有关哪些文件('target'目的文件)是从哪些别的文件('dependencies'依靠文件)中产生的,用什么命令来进行这个产生过程。有了这些信息, make 会检查磁碟上的文件,如果目的文件的时间戳(该文件生成或被改动时的时间)比至少它的一个依靠文件旧的话, make 就执行相应的命令,以便更新目的文件。(目的文件不一定是最后的可执行档,它可以是任何一个文件。)
makefile 一般被叫做"makefile"或"Makefile"。当然你可以 在 make 的命令行指定别的文件名。如果你不特别指定,它会寻找"makefile"或"Makefile",因此使用这两个名字是最简单的。
一个 makefile 主要含有一系列的规则,如下:
: ...
(tab)<command>
(tab)<command>
.
.
.
例如,考虑以下的 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 -- make 从最上面开始,把上 面第一个目的,'myprog', 做为它的主要目标(一个它需要保证其总是最新的最终目标)。给出的规则说明只要文件'myprog' 比文件'foo.o'或'bar.o'中的任何一个旧,下一行的命令将 会被执行。
但是,在检查文件 foo.o 和 bar.o 的时间戳之前,它会往下查 找那些把 foo.o 或 bar.o 做为目标文件的规则。它找到的关于 foo.o 的规则,该文件的依靠文件是 foo.c, foo.h 和 bar.h 。 它从下面再找不到生成这些依靠文件的规则,它就开始检查磁碟上这些依靠文件的时间戳。如果这些文件中任何一个的时间戳比 foo.o 的新,命令 'gcc - o foo.o foo.c' 将会执行,从而更新文件 foo.o 。
接下来对文件 bar.o 做类似的检查,依靠文件在这里是文件 bar.c 和 bar.h 。现在, make 回到'myprog'的规则。如果刚才两个规则中的任 何一个被执行,myprog就
需要重建(因为其中一个 .o 档就会比'myprog'新),因此连接命令将被执行。
2.2 编写 make 规则 (Rules)
最明显的(也是最简单的)编写规则的方法是一个一个的查看源码文件,把它们的目标文件做为目的,而C源码文件和被它 #include 的 header 档做为依靠文件。但是你也要把其它被这些 header 档 #include 的 header 档也列为依靠文件,还有那些被包括的文件所包括的文件……然后你会发现要对越来越多的文件进行管理,然后你的头发开始脱落,你的脾气开始变坏,你的脸色变成菜色,你走在路上开始跟电线杆子碰撞,终于你捣毁你的电脑显示器,停止编程。到底有没有些容易点儿的方法呢?
当然有!向编译器要!在编译每一个源码文件的时候,它实在应该知道应该包括什么样的header 档。使用 gcc 的时候,用 -M 开关,它会为每一个你给它的C文件输出一个规则,把目标文件做为目的,而这个C文件和所有应该被 #include 的 header 文件将做为依靠文件。注意这个规则会加入所有 header 文件,包 括被角括号(`<', `>')和双引号(`"')所包围的文件。其实我们可以相当肯定系统 header 档(比如 stdio.h, stdlib.h 等等)不会 被我们更改,如果你用 - MM 来代替 -M 传递给 gcc,那些用角括号包围的 header 档将不会被包括。(这会节省一些编译时间)
由 gcc 输出的规则不会含有命令部分;你可以自己写入你的命令 或者什么也不写,而让make 使用它的隐含的规则(参考下面的 2.4 节)。
2.3 Makefile 变量
上面提到 makefiles 里主要包含一些规则。它们包含的其它的东西是变量定义。makefile 里的变量就像一个环境变量(environment variable)。 事实上,环境变量在 make 过程中被解释成 make的变量。这些 变量是大小写敏感的,一般使用大写字母。它们可以从几乎任何 地方被引用,也可以被用来做很多事情,比如:
i) 贮存一个文件名列表。在上面的例子里,生成可执行文件的 规则包含一些目标文件名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc
做为命令参数。如果在这 里使用一个变数来贮存所有的目标文件名,加入新的目标 文件会变的简单而且较不易出错。
ii) 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系统里,或者如果你想使用一个不同的编译器,你必须将所有使用编译器的地方改成用新的编译器名。但是如果使用一 个变量来代替编译器名,那么你只需要改变一个地方,其 它所有地方的命令名就都改变了。
iii) 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项(例如 -Wall - O -g);如果你把这组选项存入一个变量,那么你可以把这个变量放在所有呼叫编译器 的地方。而当你要改变选项的时候,你只需在一个地方改 变这个变量的内容。要设定一个变量,你只要在一行的开始写下这个变量的名字,后面跟一个 = 号,后面跟你要设定的这个变量的值。以后你要引用这个变量,写一个 $ 符号,后面是围在括号里的变量名。比如在下面,我们把前面的 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 结束 ===
还有一些设定好的内部变量,它们根据每一个规则内容定义。三个比较有用的变量是 $@, $< 和 $^ (这些变量不需要括号括住)。 $@ 扩展成当前规则的目的文件名, $< 扩展成依靠列表中的第 一个依靠文件,而 $^ 扩展成整个依靠的列表(除掉了里面所有重复的文件名)。利用这些变量,我们可以把上面的 makefile 写成:
=== 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 结束 ===
你可以用变量做许多其它的事情,特别是当你把它们和函数混合使用的时候。如果需要更进一步的了解,请参考 GNU Make 手册。 ('man make', 'man makefile')
2.8 一个更好的 makefile
这是一个我为我大多数项目设计的 makefile 。它应该可以不需要修改的用在大部分项目里。我主要把它用在 djgpp 上,那是一个 DOS 版的gcc 编译器。因此你可以看到执行的命令名、 'alleg' 程序包、 和 RM -F 变量都反映了这一点。
=== makefile 开始 ===
######################################
#
# Generic makefile
#
# by George Foot
# email: george.foot@merton.ox.ac.uk
#
# Copyright (c) 1997 George Foot
# All rights reserved.
# 保留所有版权
#
# No warranty, no liability;
# you use this at your own risk.
# 没保险,不负责
# 你要用这个,你自己担风险
#
# You are free to modify and
# distribute this without giving
# credit to the original author.
# 你可以随便更改和散发这个文件
# 而不需要给原作者什么荣誉。
# (你好意思?)
#
######################################
### 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),)
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)
OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE)))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) \
$(patsubst %.d,%.cc,$(MISSING_DEPS)))
CPPFLAGS += -MD
.PHONY : everything deps objs clean veryclean rebuild
everything : $(EXECUTABLE)
deps : $(DEPS)
objs : $(OBJS)
clean :
@$(RM-F) *.o
@$(RM-F) *.d
veryclean: clean
@$(RM-F) $(EXECUTABLE)
rebuild: veryclean everything
ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
@$(RM-F) $(
GNU Make 的主要工作是读进一个文本文件, makefile 。这个文件里主要是有关哪些文件('target'目的文件)是从哪些别的文件('dependencies'依靠文件)中产生的,用什么命令来进行这个产生过程。有了这些信息, make 会检查磁碟上的文件,如果目的文件的时间戳(该文件生成或被改动时的时间)比至少它的一个依靠文件旧的话, make 就执行相应的命令,以便更新目的文件。(目的文件不一定是最后的可执行档,它可以是任何一个文件。)
makefile 一般被叫做"makefile"或"Makefile"。当然你可以 在 make 的命令行指定别的文件名。如果你不特别指定,它会寻找"makefile"或"Makefile",因此使用这两个名字是最简单的。
一个 makefile 主要含有一系列的规则,如下:
: ...
(tab)<command>
(tab)<command>
.
.
.
例如,考虑以下的 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 -- make 从最上面开始,把上 面第一个目的,'myprog', 做为它的主要目标(一个它需要保证其总是最新的最终目标)。给出的规则说明只要文件'myprog' 比文件'foo.o'或'bar.o'中的任何一个旧,下一行的命令将 会被执行。
但是,在检查文件 foo.o 和 bar.o 的时间戳之前,它会往下查 找那些把 foo.o 或 bar.o 做为目标文件的规则。它找到的关于 foo.o 的规则,该文件的依靠文件是 foo.c, foo.h 和 bar.h 。 它从下面再找不到生成这些依靠文件的规则,它就开始检查磁碟上这些依靠文件的时间戳。如果这些文件中任何一个的时间戳比 foo.o 的新,命令 'gcc - o foo.o foo.c' 将会执行,从而更新文件 foo.o 。
接下来对文件 bar.o 做类似的检查,依靠文件在这里是文件 bar.c 和 bar.h 。现在, make 回到'myprog'的规则。如果刚才两个规则中的任 何一个被执行,myprog就
需要重建(因为其中一个 .o 档就会比'myprog'新),因此连接命令将被执行。
2.2 编写 make 规则 (Rules)
最明显的(也是最简单的)编写规则的方法是一个一个的查看源码文件,把它们的目标文件做为目的,而C源码文件和被它 #include 的 header 档做为依靠文件。但是你也要把其它被这些 header 档 #include 的 header 档也列为依靠文件,还有那些被包括的文件所包括的文件……然后你会发现要对越来越多的文件进行管理,然后你的头发开始脱落,你的脾气开始变坏,你的脸色变成菜色,你走在路上开始跟电线杆子碰撞,终于你捣毁你的电脑显示器,停止编程。到底有没有些容易点儿的方法呢?
当然有!向编译器要!在编译每一个源码文件的时候,它实在应该知道应该包括什么样的header 档。使用 gcc 的时候,用 -M 开关,它会为每一个你给它的C文件输出一个规则,把目标文件做为目的,而这个C文件和所有应该被 #include 的 header 文件将做为依靠文件。注意这个规则会加入所有 header 文件,包 括被角括号(`<', `>')和双引号(`"')所包围的文件。其实我们可以相当肯定系统 header 档(比如 stdio.h, stdlib.h 等等)不会 被我们更改,如果你用 - MM 来代替 -M 传递给 gcc,那些用角括号包围的 header 档将不会被包括。(这会节省一些编译时间)
由 gcc 输出的规则不会含有命令部分;你可以自己写入你的命令 或者什么也不写,而让make 使用它的隐含的规则(参考下面的 2.4 节)。
2.3 Makefile 变量
上面提到 makefiles 里主要包含一些规则。它们包含的其它的东西是变量定义。makefile 里的变量就像一个环境变量(environment variable)。 事实上,环境变量在 make 过程中被解释成 make的变量。这些 变量是大小写敏感的,一般使用大写字母。它们可以从几乎任何 地方被引用,也可以被用来做很多事情,比如:
i) 贮存一个文件名列表。在上面的例子里,生成可执行文件的 规则包含一些目标文件名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc
做为命令参数。如果在这 里使用一个变数来贮存所有的目标文件名,加入新的目标 文件会变的简单而且较不易出错。
ii) 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系统里,或者如果你想使用一个不同的编译器,你必须将所有使用编译器的地方改成用新的编译器名。但是如果使用一 个变量来代替编译器名,那么你只需要改变一个地方,其 它所有地方的命令名就都改变了。
iii) 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项(例如 -Wall - O -g);如果你把这组选项存入一个变量,那么你可以把这个变量放在所有呼叫编译器 的地方。而当你要改变选项的时候,你只需在一个地方改 变这个变量的内容。要设定一个变量,你只要在一行的开始写下这个变量的名字,后面跟一个 = 号,后面跟你要设定的这个变量的值。以后你要引用这个变量,写一个 $ 符号,后面是围在括号里的变量名。比如在下面,我们把前面的 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 结束 ===
还有一些设定好的内部变量,它们根据每一个规则内容定义。三个比较有用的变量是 $@, $< 和 $^ (这些变量不需要括号括住)。 $@ 扩展成当前规则的目的文件名, $< 扩展成依靠列表中的第 一个依靠文件,而 $^ 扩展成整个依靠的列表(除掉了里面所有重复的文件名)。利用这些变量,我们可以把上面的 makefile 写成:
=== 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 结束 ===
你可以用变量做许多其它的事情,特别是当你把它们和函数混合使用的时候。如果需要更进一步的了解,请参考 GNU Make 手册。 ('man make', 'man makefile')
2.8 一个更好的 makefile
这是一个我为我大多数项目设计的 makefile 。它应该可以不需要修改的用在大部分项目里。我主要把它用在 djgpp 上,那是一个 DOS 版的gcc 编译器。因此你可以看到执行的命令名、 'alleg' 程序包、 和 RM -F 变量都反映了这一点。
=== makefile 开始 ===
######################################
#
# Generic makefile
#
# by George Foot
# email: george.foot@merton.ox.ac.uk
#
# Copyright (c) 1997 George Foot
# All rights reserved.
# 保留所有版权
#
# No warranty, no liability;
# you use this at your own risk.
# 没保险,不负责
# 你要用这个,你自己担风险
#
# You are free to modify and
# distribute this without giving
# credit to the original author.
# 你可以随便更改和散发这个文件
# 而不需要给原作者什么荣誉。
# (你好意思?)
#
######################################
### 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),)
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)
OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE)))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) \
$(patsubst %.d,%.cc,$(MISSING_DEPS)))
CPPFLAGS += -MD
.PHONY : everything deps objs clean veryclean rebuild
everything : $(EXECUTABLE)
deps : $(DEPS)
objs : $(OBJS)
clean :
@$(RM-F) *.o
@$(RM-F) *.d
veryclean: clean
@$(RM-F) $(EXECUTABLE)
rebuild: veryclean everything
ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
@$(RM-F) $(