摘要
文章除介绍make工具是如何运行的以外,还涉及到make工具除了具有软件开发作用外的许多别的用途。
介绍
几乎每个使用Linux的用户都会使用到make工具。当然我们也知道只有在程序和内核建立于源代码的基础上及软件包安装完成后,make工具才能运行。Make对于软件开发是一个非常重要的操作工具。然而,make还能做更多你先前没有注意过的事。
你所将要了解的是,make工具将成为你以后处理日常工作如写文章、著书或建立一个Web站点的一个非常有效的工具。你会学到许多‘unix窍门',它会让你在以后的工作中更熟练的操作make工具。要注意的是:在此我们谈及的虽然只是Linux,但原则上我们可以在任何操作系统中使用make工具。
示例: 建立一个Web站点
一个Web站点必须由许多分工不同的人共同维护。Jan负责两个栏目,Piet负责布局。
我们需要一个简单的系统去区分布局和内容,一个有效的措施是:从数据库中读取内容,这对于页面是必须的。例如,PHP和Microsoft Active Server Pages 就可以实现这个功能。然而,我们仅可能储存静态的HTML(HyperText Markup Language)。而且,内容不会改变以便有效的维护数据库。
通过简单的命令建立Web站点:
◆ 布局设计
Jan的文章都保存在他的根目录:offer.htem和indew.html中。而所有文件都保存在目录/hone/jan/Docs/webbsite/中。
index.html包括一些新闻和一个欢迎辞。offer.html提供每周的what-do-I-know potatos 的照片。Piet负责布局设计和建立web站点。最后,页面在根目录/home/piet/public-html中公布。
◆ 模板
Piet要求Jan在HTML中写他的页面,这需要一个与之相配的布局。
例如,Piet将站点的header文件放于header.html中,将footer文件放在footer.html中。header.html如下:
〈html>〈!--the header--〉
〈head>
This is our website
Some rubbish is written down here.
We are very interactive
so this is our telephone number:
0123-456789
footer.html设置如下:
再如,下面unix系统命令从Jan的index.html中建立:
cat header.html /home/jan/Docs/website/index.html
echo -n '
--------------------------------------------------------------------------------
Last modification: '
date '+%A %e %B'
cat footer.html
请查阅命令使用手册。执行完以上命令的结果是最终的文件将被输送到标准输出端中,被放置于一个文件里。
{
cat header.html /home/jan/Docs/website/index.html
echo -n '
--------------------------------------------------------------------------------
Last modification: '
date '+%A %e %B'
cat footer.html
} > /home/piet/public_html/index.html
这个程序将和其他的文件一起被再三重用,offer.html。事实上,我们生成了一个小小的脚本使web站点得以建立。
然而,用人工操作这些命令是不实际的。我们可以设立一个脚本程序,这样当Jan更新他的指数的时候脚本会自动发挥作用。而且,当Piet想更改header文件或footer文件的时候这个脚本同样可以发挥作用!另一方面,假如Jan 不做修改,那么这个脚本不会发挥作用。
第一次亲密接触make
GNUD的make工具的信息手册是一个让人费解的文献。从一开始它就着重于一个程序环境,出于这个原因,我将简要的说明一下make工具的功能:
make确定一组命令是否需要执行,是依赖于源文件的创建时间和目标文件的创建时间的对比。
换句话说就是当源文件比目标文件新的话,这一组命令将被执行。目的是更新该目标文件。
目标文件是对象,源文件是先决条件(第一命令)。如果先决条件之一比目标文件要新(或着目标不存在)那么命令将被执行。而如果所有的先决条件都比目标要旧或着和目标一样旧,那么命令不被执行,目标将被更新。
在当前的工作目录下,将产生一个名为Makefile的文件。make工具工作的时候,这个文件中的信息将被使用到。一旦我们有了makefile文件,那么我们唯一要做的就是:键入‘make'和相关命令,生成目标文件,这些都将被自动执行。
使用下面的命令行来调用make:
make target1 target2……
目标是可选择的(如果目标文件没有指定,那么将使用Makefile中的第一个目标)Make习惯在当前目录中搜寻Makefile文件。很有可能会提供多个目标。
Makefile的衔接
Makefile将由一个编辑器生成:
# This is an example of a Makefile.
# Comments can be put after a hash (#).
target: prerequisites
command
target: prerequisites
commando
# and so on and so on.
我们把目标文件放在最前面,后边紧接着一个“:”及所需的先决条件。在先决条件存在的情况下,用一个反斜线符号(/)结束一个命令行然后开始另一个命令行。
在接下来的命令行中,将具有一个或多个命令。每一行都被认为是一个标准命令。假如你想使用多行来定义一个命令,那么你可以在行末使用反斜线符号(//)。Make工具将把这些命令衔接起来。在这种情况下,我们用分号来分离这些命令,以便在运行shell的时候避免出现错误。
注意:命令用TAB缩进,而不是用8个空格。
Make读取Makefile文件而且确定每一个目标命令是否被执行。目标文件、先决条件、标准被指示为规则(rule)。
如果make工具在没有参数的情况下使用,那么只有第一个目标文件被执行。
我们例子中的Makefile文件
编辑Makefile文件内容如下:
# This Makefile builds Piets' and Jans' website, the potato-eaters.
all: /home/piet/public_html/index.html /home/piet/public_html/offer.html
/home/piet/public_html/index.html: header.html footer.html
/home/jan/Docs/website/index.html
{
cat header.html /home/jan/Docs/website/index.html ;
echo -n '
--------------------------------------------------------------------------------
Last modification: ' ;
date '+%A %e %B' ;
cat footer.html ;
} > /home/piet/public_html/index.html
/home/piet/public_html/offer.html: header.html footer.html
/home/jan/Docs/website/offer.html
{
cat header.html /home/jan/Docs/website/index.html ;
echo -n '
--------------------------------------------------------------------------------
Last modification: ' ;
date '+%A %e %B' ;
cat footer.html ;
} > /home/piet/public_html/offer.html
# the end
现在,我们有了三个目标文件:'all'、index.html和offer.html。'all'唯一的作用就是将其他两个文件作为先决条件。因为'all'本身不是文件名,所以目标文件'all'将被执行。(随后我们将介绍一种更别致的方法用来定义并非文件的目标)
如果header文件和footer文件被修改,那么整个页面都将被更新。但如果只改变其中的一页,那么仅这一页被更新。
但是,Makefile文件有一个缺点:它不容易被监查。所幸的是,有许多方式可以有效而简单的解决这个问题!
Makefile的制作流程
☆变量
使用变量,Makefile将得到很大的简化。变量定义如下:
variable = value
我们使用表达式$(variabele)来引用变量。如果我们将变量并入Makefile中,操作如下:
# This Makefile builds Piets' and Jans' website, the potato-eaters.
# Directory where the website is stored:
TARGETDIR = /home/piet/public_html
# Jans' directory:
JANSDIR = /home/jan/Docs/website
# Files needed for the layout:
LAYOUT = header.html footer.html
all: $(TARGETDIR)/index.html $(TARGETDIR)/offer.html
$(TARGETDIR)/index.html: $(LAYOUT) $(JANSDIR)/index.html
{
cat header.html $(JANSDIR)/index.html ;
echo -n '
--------------------------------------------------------------------------------
Last modification: ' ;
date '+%A %e %B' ;
cat footer.html ;
} > $(TARGETDIR)/index.html
$(TARGETDIR)/offer.html: $(LAYOUT) $(JANSDIR)/offer.html
{
cat header.html $(JANSDIR)/index.html ;
echo -n '
--------------------------------------------------------------------------------
Last modification: ' ;
date '+%A %e %B' ;
cat footer.html ;
} > $(TARGETDIR)/offer.html
# the end
要养成用大写字母来书写变量的好习惯。这样改动起来的时候会容易得多。
如果你愿意,你可以定义另外一种方法将各个文档放在恰当的布局。但如果你想将许多变量放在一个布局中,那又该怎么做呢?当有许多副本的时候makefile文件将变得非常庞大。但是,所有这些问题也可以变得很简单。
☆模式规则
模式规则使我们对不同的目标文件使用同样的一系列命令。
使用模式规则,将改变行的衔接;加入一个额外的pattern field:
Multiple targets: pattern : prerequisite prerequisite ...
command
这个模式是一个表达式,它适用于任何目标文件。百分号被用于合并目标文件名的不同部分。
例如:
/home/bla/target1.html /home/bla/target2.html: /home/bla/% : %
commands
当make工具读到这的时候,行将扩张到两行。在此,模式将确定目标文件的哪一个部分将用百分号合并。
当百分号出现在先决条件区段(prerequisites-field)中时,表示这一部分已经被百分号复制了下来。
Make工具发展如下:
/home/bla/target1.html: target1.html
commands
/home/bla/target2.html: target2.html
模式‘/home/bla/%'中的百分号和目标‘/home/bla/target1.html'中的‘target1.html',从而‘%'发展为‘target.html'的必备条件。
适用我们的web站点,下面的规则合并为:
$(TARGETDIR)/index.html $(TARGETDIR)/offer.html:
$(TARGETDIR)/% : $(JANSDIR)/%
$(LAYOUT)
现在我们还剩一个问题:在命令中如何使用这些变量?命令和目标文件都有一些不同吗?
☆自动变量
幸运的是,make能自动定义变量。这些变量中的某些被称为自动变量。这些变量在命令执行过程中(最好:刚好在执行命令之前)包含目标的数量及(或者)先决条件。
简单的变量$< 象征第一先决条件,变量$@ 为目前的目标。
使用这些变量可将下全部的规则归纳如下:
$(TARGETDIR)/index.html $(TARGETDIR)/offer.html: $(TARGETDIR)/% :
$(JANSDIR)/%
$(LAYOUT)
{
cat header.html $< ;
echo -n '
--------------------------------------------------------------------------------
Last modification: ' ;
date '+%A %e %B' ;
cat footer.html ;
} > $@
为了完整性,要呈现整个Makefile文件,包括一些optimalisations:
# This Makefile builds Piets' and Jans' website, the potato-eaters.
# Directory where the website is published:
TARGETDIR = /home/piet/public_html
# Jans' directory:
JANSDIR = /home/jan/Docs/website
# Files needed for the layout:
LAYOUT = header.html footer.html
# These are the webpages:
DOCS = $(TARGETDIR)/index.html $(TARGETDIR)/offer.html
# Please change nothing below this line;-)
# -------------------------------------------------------------
all: $(DOCS)
$(DOCS): $(TARGETDIR)/% : $(JANSDIR)/% $(LAYOUT)
{
cat header.html $< ;
echo -n '
--------------------------------------------------------------------------------
Last modification: ' ;
date '+%A %e %B' ;
cat footer.html ;
} > $@
# the end
就算要加入更多的文件,并将它们合并到Makefile文件中,只要使用DOCS变量还是很简单的,无须很多的键入。
你看,维护Makefile文件是一件多么简单的事,你根本不用再为它是怎么工作的而感到困惑!
☆最后的小小优化
我们更喜欢在DOCS中mension文档,这样不用包含整个目录。操作如下(我们在TEXTS中的makefile文件的开头改变DOCS):
# Please change nothing below this line;-)
# -------------------------------------------------------------
DOCS = $(addprefix $(TARGETDIR)/,$(TEXTS))
all: $(DOCS)
# and so on
我们在此看到的是make工具的一个独特的功能:它可以在括号间使用一个完全的表达式来代替变量名。这样,我们可以通过很多方式来修改文本。
这个独特的命令$(addprefix prefix,list)为列在列表上的每个元素加上一个前缀。例如,TARGETDIR变量加上一个斜线(/)。
所列条目用空格分开。出于这个原因,使用make 工具加工带有空格的文件名将会出现问题。
在前面我们提到目标文件'all'并不能产生一个名为'all'的文件(这行并不包括任何命令 ),结果是该目标文件将被始终执行。但是当自动生成一个以此文件名命名的文件,而且它比其它文件新的时候,我们又该怎样操作?
有一种很简单的方式定义make 始终执行一个特定的目标文件,而且这个目标文件并不指定硬盘中的文件。操作起来是将这个目标文件标示为'phony'(假名):
.PHONY:all
这样,整个MakeFile文件变成如下所示:
# This Makefile builds Piets' and Jans' website, the potato-eaters.
# Directory where the website is published:
TARGETDIR = /home/piet/public_html
# Jans' directory:
JANSDIR = /home/jan/Docs/website
# Files needed for the layout:
LAYOUT = header.html footer.html
# These are the names of the webpages:
TEXTS = index.html offer.html yetanotherfile.html
# Please change nothing below this line;-)
# -------------------