Make命令完全详解教程

无论是在Linux还是在Unix环境中,make都是一个非常重要的编译命令。不管是自己进行项目开发还是安装应用软件,我们都经常要用到make或make install。利用make工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用make和makefile工具就可以简洁明快地理顺各个源文件之间纷繁复杂的相互关系。而且如此多的源文件,如果每次都要键入gcc命令进行编译的话,那对程序员来说简直就是一场灾难。而make工具则可自动完成编译工作,并且可以只对程序员在上次编译后修改过的部分进行编译。因此,有效的利用make和makefile工具可以大大提高项目开发的效率。同时掌握make和makefile之后,您也不会再面对着Linux下的应用软件手足无措了。

 

 

 

 

 

 

 

一、Make程序的命令行选项和参数

Make命令参数的典型序列如下所示:

make [-f makefile文件名][选项][宏定义][目标]

这里用[]括起来的表示是可选的。命令行选项由破折号“–”指明,后面跟选项,如

make –e

如果需要多个选项,可以只使用一个破折号,如

make –kr

也可以每个选项使用一个破折号,如

make –k –r

甚至混合使用也行,如

make –e –kr

Make命令本身的命令行选项较多,这里只介绍在开发程序时最为常用的三个,它们是:

–k:

如果使用该选项,即使make程序遇到错误也会继续向下运行;如果没有该选项,在遇到第一个错误时make程序马上就会停止,那么后面的错误情况就不得而知了。我们可以利用这个选项来查出所有有编译问题的源文件。

–n:

该选项使make程序进入非执行模式,也就是说将原来应该执行的命令输出,而不是执行。

–f :

指定作为makefile的文件的名称。 如果不用该选项,那么make程序首先在当前目录查找名为makefile的文件,如果没有找到,它就会转而查找名为Makefile的文件。如果您在Linux下使用GNU Make的话,它会首先查找GNUmakefile,之后再搜索makefile和Makefile。按照惯例,许多Linux程序员使用Makefile,因为这样能使Makefile出现在目录中所有以小写字母命名的文件的前面。所以,最好不要使用GNUmakefile这一名称,因为它只适用于make程序的GNU版本。

当我们想构建指定目标的时候,比如要生成某个可执行文件,那么就可以在make命令行中给出该目标的名称;如果命令行中没有给出目标的话,make命令会设法构建makefile中的第一个目标。我们可以利用这一特点,将all作为makefile中的第一个目标,然后将让目标作为all所依赖的目标,这样,当命令行中没有给出目标时,也能确保它会被构建。

二、Makefile概述

上面提到,make命令对于构建具有多个源文件的程序有很大的帮助。事实上,只有make命令还是不够的,前面说过还必用须makefile告诉它要做什么以及怎么做才行,对于程序开发而言,就是告诉make命令应用程序的组织情况。

我们现在对makefile的位置和数量简单说一下。一般情况下,makefile会跟项目的源文件放在同一个目录中。另外,系统中可以有多个makefile,一般说来一个项目使用一个makefile就可以了;如果项目很大的话,我们就可以考虑将它分成较小的部分,然后用不同的makefile来管理项目的不同部分。

make命令和Makefile配合使用,能给我们的项目管理带来极大的便利,除了用于管理源代码的编译之外,还用于建立手册页,同时还能将应用程序安装到指定的目录。

因为Makefile用于描述系统中模块之间的相互依赖关系,以及产生目标文件所要执行的命令,所以,一个makefile由依赖关系和规则两部分内容组成。下面分别加以解释。

依赖关系由一个目标和一组该目标所依赖的源文件组成。这里所说的目标就是将要创建或更新的文件,最常见的是可执行文件。规则用来说明怎样使用所依赖得文件来建立目标文件。

当make命令运行时,会读取makefile来确定要建立的目标文件或其他文件,然后对源文件的日期和时间进行比较,从而决定使用那些规则来创建目标文件。一般情况下,在建立起最终的目标文件之前,肯定免不了要建立一些中间性质的目标文件。这时,Make命令也是使用makefile来确定这些目标文件的创建顺序,以及用于它们的规则序列。

2.1.Makefile中的依赖关系

make程序自动生成和维护通常是可执行模块或应用程序的目标,目标的状态取决于它所依赖的那些模块的状态。Make的思想是为每一块模块都设置一个时间标记,然后根据时间标记和依赖关系来决定哪一些文件需要更新。一旦依赖模块的状态改变了,make就会根据时间标记的新旧执行预先定义的一组命令来生成新的目标。

依赖关系规定了最终得到的应用程序跟生成它的各个源文件之间的关系。如下面的图1描述了可执行文件main对所有的源程序文件及其编译产生的目标文件之间的依赖关系,见下图:

图1  模块间的依赖关系

就图1而言,我们可以说可执行程序main依赖于main.o、f1.o和ff1.o。与此同时,main.o依赖于main.c和def1.h;f1.o依赖于f1.c、def1.h和def2.h;而ff1.o则依赖于ff1.c、def2.h和def3. h。在makefile中,我们可以用目标名称,加冒号,后跟空格键或tab键,再加上由空格键或tab键分隔的一组用于生产目标模块的文件来描述模块之间的依赖关系对于上例来说,可以作以下描述:

main: main.o f1.o f2.o

main.o: main.c def1.h

f1.o: f1.c def1.h def2.h

f2.o: f2.c def2.h def3.h

不难发现,上面的各个源文件跟各模块之间的关系具有一个明显的层次结构,如果def2.h发生了变化,那么就需要更新f1.o和f2.o,而f1.o和f2.o发生了变化的话,那么main也需要随之重新构建。

默认时,make程序只更新makefile中的第一个目标,如果希望更新多个目标文件的话,可以使用一个特殊的目标all,假如我们想在一个makefile中更新main和hello这两个程序文件的话,可以加入下列语句达到这个目的:

all: main hello

实际上,makefile是以相关行为基本单位的,相关行用来描述目标、模块及规则(即命令行)三者之间的关系。一个相关行格式通常为:冒号左边是目标(模块)名;冒号右边是目标所依赖的模块名;紧跟着的规则(即命令行)是由依赖模块产生目标所使用的命令。相关行的格式为:

目标:[依赖模块][;命令]

习惯上写成多行形式,如下所示:

目标:[依赖模块]

命令

命令

目标(TARGET)程序产生的文件,如可执行文件和目标文件;目标也可以是要执行的动作,如“clean”。

依赖(DEPENDENCIES)是用来产生目标的输入文件,一个目标通常依赖于多个文件。

命令(COMMAND)是make执行的动作,一个可以有多个命令,每个占一行。注意:每个命令行的起始字符必须为TAB字符!

有依赖关系规则中的命令通常在依赖文件变化时负责产生target文件,make执行这些命令更新或产生target。规则可以没有依赖关系,如包含target “clean”的规则。

规则解释如何和何时重做该规则中的文件,make根据依赖关系执行产生或更新目标;规则也说明如何和何时执行动作。有的规则看起来很复杂,但都符合上述模式。

需要注意的是,如果相关行写成一行,“命令”之前用分号“;”隔开,如果分成多行书写的话,后续的行务必以tab字符为先导。对于makefile而言,空格字符和tab字符是不同的。所有规则所在的行必须以tab键开头,而不是空格键。初学者一定对此保持警惕,因为这是新手最容易疏忽的地方,因为几个空格键跟一个tab键在肉眼是看不出区别的,但make命令却能明察秋毫。

此外,如果在makefile文件中的行尾加上空格键的话,也会导致make命令运行失败。所以,大家一定要小心了,免得耽误许多时间。

例,一个名为prog的程序由三个C源文件filea.c、fileb.c和filec.c以及库文件LS编译生成,这三个文件还分别包含自己的头文件a.h 、b.h和c.h。通常情况下,C编译器将会输出三个目标文件filea.o、fileb.o和filec.o。假设filea.c和fileb.c都要声明用到一个名为defs的文件,但filec.c不用。即在filea.c和fileb.c里都有这样的声明:

 #include "defs"

  那么下面的文档就描述了这些文件之间的相互联系:

line 

1   #It is a example for describing makefile

2   prog : filea.o fileb.o filec.o

3   cc filea.o fileb.o filec.o -LS -o prog

4   filea.o : filea.c a.h defs

5   cc -c filea.c

6   fileb.o : fileb.c b.h defs

7   cc -c fileb.c

8   filec.o : filec.c c.h

9    cc -c filec.c

这个描述文档就是一个简单的makefile文件。

从上面的例子注意到,第一个字符为 # 的行为注释行。第一个非注释行指定prog由三个目标文件filea.o、fileb.o和filec.o链接生成。第三行描述了如何从prog所依赖的文件建立可执行文件。接下来的4、6、8行分别指定三个目标文件,以及它们所依赖的.c和.h文件以及defs文件。而5、7、9行则指定了如何从目标所依赖的文件建立目标。

当filea.c或a.h文件在编译之后又被修改,则 make 工具可自动重新编译filea.o,如果在前后两次编译之间,filea.C 和a.h 均没有被修改,而且 test.o 还存在的话,就没有必要重新编译。这种依赖关系在多源文件的程序编译中尤其重要。通过这种依赖关系的定义,make 工具可避免许多不必要的编译工作。当然,利用 Shell 脚本也可以达到自动编译的效果,但是,Shell 脚本将全部编译任何源文件,包括哪些不必要重新编译的源文件,而 make 工具则可根据目标上一次编译的时间和目标所依赖的源文件的更新时间而自动判断应当编译哪个源文件。

2.2.Makefile文件举例

根据图1的依赖关系,这里给出了一个完整的makefile文件,这个例子很简单,由四个相关行组成,我们将其命名为mymakefile1。文件内容如下所示:

main: main.o f1.o f2.o

gcc -o main main.o f1.o f2.o

main.o: main.c def1.h

gcc -c main.c

f1.o: f1.c def1.h def2.h

gcc -c f1.c

f2.o: f2.c def2.h def3.h

gcc -c f2.c

注意,由于我们这里没有使用缺省名makefile 或者Makefile ,所以一定要在make命令行中加上-f选项。如果在没有任何源码的目录下执行命令“make -f Mymakefile1”的话,将收到下面的消息:

make: *** No rule to make target ‘main.c’, needed by ‘main.o’. Stop.

Make命令将makefile中的第一个目标即main作为要构建的文件,所以它会寻找构建该文件所需要的其他模块,并判断出必须使用一个称为main.c的文件。因为迄今尚未建立该文件,而makefile又不知道如何建立它,所以只好报告错误。好了,现在建立这个源文件,为简单起见,我们让头文件为空,创建头文件的具体命令如下:

$ touch def1.h

$ touch def2.h

$ touch def3.h

我们将main函数放在main.c文件中,让它调用function2和function3,但将这两个函数的定义放在另外两个源文件中。由于这些源文件含有#include命令,所以它们肯定依赖于所包含的头文件。如下所示:

/* main.c */

#include <STDLIDEF2.H>

#include “def1.h”

extern void function2();

extern void function3();

int main()

{

function2();

function3();

exit (EXIT_SUCCESS);

}

/* f1.c */

#include “def1.h”

#include “def2.h”

void function2() {

}

/* f2.c */

#include “def2.h”

#include “def3.h”

void function3()

建好源代码后,再次运行make程序,看看情况如何:

$ make -f Mymakefile1

gcc -c main.c

gcc -c f1.c

gcc -c f2.c

gcc -o main main.o f1.o f2.o

$

好了,这次顺利通过了。这说明Make命令已经正确处理了makefile描述的依赖关系,并确定出了需要建立哪些文件,以及它们的建立顺序。虽然我们在makefile 中首先列出的是如何建立main,但是make还是能够正确的判断出这些文件的处理顺序,并按相应的顺序调用规则部分规定的相应命令来创建这些文件。当这些命令执行时,make程序会按照执行情况来显示这些命令。

如今,我们对def2.h加以变动,来看看makefile能否对此作出相应的回应:

$ touch def2.h

$ make -f Mymakefile1

gcc -c f1.c

gcc -c f2.c

gcc -o main main.o f1.o f2.o

$

这说明,当Make命令读取makefile 后,只对受def2.h的变化的影响的模块进行了必要的更新,注意它的更新顺序,它先编译了C程序,最后连接生产了可执行文件。现在,让我们来看看删除目标文件后会发生什么情况,先执行删除,命令如下:

$ rm f1.o

然后运行make命令,如下所示:

$ make -f Mymakefile1

gcc -c f1.c

gcc -o main main.o f1.o f2.o

$

很好,make的行为让我们非常满意。

2.3.Makefile中的宏

在makefile中可以使用诸如XLIB、UIL等类似于Shell变量的标识符,这些标识符在makefile中称为“宏”,它可以代表一些文件名或选项。宏的作用类似于C语言中的define,利用它们来代表某些多处使用而又可能发生变化的内容,可以节省重复修改的工作,还可以避免遗漏。

Make的宏分为两类,一类是用户自己定义的宏,一类是系统内部定义的宏。用户定义的宏必须在makefile或命令行中明确定义,系统定义的宏不由用户定义。我们首先介绍第一种宏。

这里是一个包含宏的makefile文件,我们将其命名为mymakefile2,如下所示:

all: main

# 使用的编译器

CC = gcc

#包含文件所在目录

INCLUDE = .

# 在开发过程中使用的选项

CFLAGS = -g -Wall –ansi

# 在发行时使用的选项

# CFLAGS = -O -Wall –ansi

main: main.o f1.o f2.o

$(CC) -o main main.o f1.o f2.o

main.o: main.c def1.h

$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c

f1.o: f1.c def1.h def2.h

$(CC) -I$(INCLUDE) $(CFLAGS) -c f1.c

f2.o: f2.c def2.h def3.h

$(CC) -I$(INCLUDE) $(CFLAGS) -c f2.c

我们看到,在这里有一些注释。在makefile中,注释以#为开头,至行尾结束。注释不仅可以帮助别人理解我们的makefile,如果时间久了,有些东西我们自己也会忘掉,它们对makefile的编写者来说也是很有必要的。

现在言归正传,先看一下宏的定义。我们既可以在make命令行中定义宏,也可以在makefile中定义宏。

2.4.在makefile中定义宏的基本语法是:

宏标识符=值列表

其中,宏标识符即宏的名称通常全部大写,但它实际上可以由大、小写字母、阿拉伯数字和下划线构成。等号左右的空白符没有严格要求,因为它们最终将被make删除。至于值列表,既可以是零项,也可以是一项或者多项。如:

LIST_VALUE = one two three

当一个宏定义之后,我们就可以通过$(宏标识符)或者${宏标识符}来访问这个标识符所代表的值了。

在makefile中,宏经常用作编译器的选项。很多时候,处于开发阶段的应用程序在编译时是不用优化的,但是却需要调试信息;而正式版本的应用程序却正好相反,没有调试信息的代码不仅所占内存较小,进过优化的代码运行起来也更快。

对于Mymakefile1来说,它假定所用的编译器是gcc,不过在其他的UNIX系统上,更常用的编译器是cc或者c89,而非gcc。如果你想让自己的makefile适用于不同的UNIX操作系统,或者在一个系统上使用其他种类的编译器,这时就不得不对这个makefile中的多处进行修改。

但对于mymakefile2来说则不存在这个问题,我们只需修改一处,即宏定义的值就行了。除了在makefile中定义宏的值之外,我们还可以在make命令行中加以定义,如:

$ make CC=c89

当命令行中的宏定义跟makefile中的定义有冲突时,以命令行中的定义为准。当在makefile文件之外使用时,宏定义必须作为单个参数进行传递,所以要避免使用空格,但是更妥当的方法是使用引号,如:

$ make “CC =   c89”

这样就不必担心空格所引起的问题了。现在让我们将前面的编译结果删掉,来测试一下mymakefile2的工作情况。命令如下所示:

$ rm *.o main

$ make -f Mymakefile2

gcc -I. -g -Wall -ansi -c main.c

gcc -I. -g -Wall -ansi -c f1.c

gcc -I. -g -Wall -ansi -c f2.c

gcc -o main main.o f1.o f2.o

$

就像我们看到的那样,Make程序会用相应的定义来替换宏引用$(CC )、$(CFLAGS )和$(INCLUDE),这跟C语言中的宏的用法比较相似。

上面介绍了用户定义的宏,现在介绍make的内部宏。常用的内部宏有:

$? :比目标的修改时间更晚的那些依赖模块表。

$@ :当前目标的全路径名。可用于用户定义的目标名的相关行中。

$< :比给定的目标文件时间标记更新的依赖文件名。

$* :去掉后缀的当前目标名。例如,若当前目标是pro.o,则$*表示pro。

Makefile文件作为一种描述文档一般需要包含以下内容:

  ◆ 宏定义

  ◆ 源文件之间的相互依赖关系

  ◆ 可执行的命令

Makefile中允许使用简单的宏指代源文件及其相关编译信息,在Linux中也称宏为变量。在引用宏时只需在变量前加$符号,但值得注意的是,如果变量名的长度超过一个字符,在引用时就必须加圆括号()。

下面都是有效的宏引用:

  $(CFLAGS)

  $2

  $Z

  $(Z)

其中最后两个引用是完全一致的。

需要注意的是一些宏的预定义变量,在Unix系统中,$*、$@、$?和$<四个特殊宏的值在执行命令的过程中会发生相应的变化,而在GNU make中则定义了更多的预定义变量。关于预定义变量的详细内容,宏定义的使用可以使我们脱离那些冗长乏味的编译选项,为编写makefile文件带来很大的方便。

# Define a macro for the object files

   OBJECTS= filea.o fileb.o filec.o

   # Define a macro for the library file

   LIBES= -LS

   # use macros rewrite makefile

   prog: $(OBJECTS)

   cc $(OBJECTS) $(LIBES) -o prog

此时如果执行不带参数的make命令,将连接三个目标文件和库文件LS;但是如果在make命令后带有新的宏定义:

  make "LIBES= -LL -LS"

则命令行后面的宏定义将覆盖makefile文件中的宏定义。若LL也是库文件,此时make命令将连接三个目标文件以及两个库文件LS和LL。

在Unix系统中没有对常量NULL作出明确的定义,因此我们要定义NULL字符串时要使用下述宏定义:

STRINGNAME=

Make命令

在make命令后不仅可以出现宏定义,还可以跟其他命令行参数,这些参数指定了需要编译的目标文件。其标准形式为:

target1 [target2 …]:[:][dependent1 …][;commands][#…]

[(tab) commands][#…]

方括号中间的部分表示可选项。Targets和dependents当中可以包含字符、数字、句点和"/"符号。除了引用,commands中不能含有"#",也不允许换行。

在通常的情况下命令行参数中只含有一个":",此时command序列通常和makefile文件中某些定义文件间依赖关系的描述行有关。如果与目标相关连的那些描述行指定了相关的command序列,那么就执行这些相关的command命令,即使在分号和(tab)后面的aommand字段甚至有可能是NULL。如果那些与目标相关连的行没有指定command,那么将调用系统默认的目标文件生成规则。

如果命令行参数中含有两个冒号"::",则此时的command序列也许会和makefile中所有描述文件依赖关系的行有关。此时将执行那些与目标相关连的描述行所指向的相关命令。同时还将执行build-in规则。

如果在执行command命令时返回了一个非"0"的出错信号,例如makefile文件中出现了错误的目标文件名或者出现了以连字符打头的命令字符串,make操作一般会就此终止,但如果make后带有"-i"参数,则make将忽略此类出错信号。

Make命本身可带有四种参数:标志、宏定义、描述文件名和目标文件名。其标准形式为:

     Make [flags] [macro definitions] [targets]

Unix系统下标志位flags选项及其含义为:

-f file 指定file文件为描述文件,如果file参数为"-"符,那么描述文件指向标准输入。如果没有"-f"参数,则系统将默认当前目录下名为makefile或者名为Makefile的文件为描述文件。在Linux中, GNU make 工具在当前工作目录中按照GNUmakefile、makefile、Makefile的顺序搜索 makefile文件。

-i   忽略命令执行返回的出错信息。

-s   沉默模式,在执行之前不输出相应的命令行信息。

-r   禁止使用build-in规则。

-n   非执行模式,输出所有执行命令,但并不执行。

-t   更新目标文件。

-q   make操作将根据目标文件是否已经更新返回"0"或非"0"的状态信息。

-p   输出所有宏定义和目标文件描述。

-d   Debug模式,输出有关文件和检测时间的详细信息。

Linux下make标志位的常用选项与Unix系统中稍有不同,下面我们只列出了不同部分:

-c dir   在读取 makefile 之前改变到指定的目录dir。

-I dir   当包含其他 makefile文件时,利用该选项指定搜索目录。

-h   help文挡,显示所有的make选项。

-w   在处理 makefile 之前和之后,都显示工作目录。

Linux下make标志位的常用选项与Unix系统中稍有不同,下面我们只列出了不同部分:

-c dir   在读取 makefile 之前改变到指定的目录dir。

-I dir   当包含其他 makefile文件时,利用该选项指定搜索目录。

-h   help文挡,显示所有的make选项。

-w   在处理 makefile 之前和之后,都显示工作目录。

通过命令行参数中的target ,可指定make要编译的目标,并且允许同时定义编译多个目标,操作时按照从左向右的顺序依次编译target选项中指定的目标文件。如果命令行中没有指定目标,则系统默认target指向描述文件中第一个目标文件。

通常,makefile 中还定义有 clean 目标,可用来清除编译过程中的中间文件,例如:

  

clean:

rm -f *.o

运行 make clean 时,将执行 rm -f *.o 命令,最终删除所有编译过程中产生的所有中间文件。

2.5.清理

编写规则不至于编译程序。Makefile通常描述如何做其它事情:比如删除目录中的目标文件和可执行文件来清理目录。例子中是这样写的:

clean:

rm edit $(objects)

实际情况是,我们需要处理一些意外事件:存在一个叫做’clean’的文件;如果rm出错,并不希望make过程停止下来,修改过的版本如下:

.PHONY : clean

clean :

-rm edit $(objects)

这样的规则当然不能放在makefile的开始,因为这并不是我们缺省要做的工作。由于’clean’并不是’edit’的依赖,在运行make时没有参数时,

这条规则不会执行;要执行这个规则,必须运行’make clean’。

 

 

 

 

三、规则

makefile中的规则描述如何生成特定的文件,即规则的目标。规则列出了目标的依赖文件,指定生成或更新目标的命令。

规则的次序是不重要的,除非是确定缺省目标:缺省目标是第一个makefile中的第一个规则;如果第一个规则有多个目标,第一个目标是缺省的。

有两个例外:以’.’开头的目标不是缺省目标;模式规则对缺省目标没有影响。

通常我们所写的地一个规则是编译整个或makefile中指定的所有程序。

3.1.例子

foo.o : foo.c defs.h # module for twiddling the frobs

cc -c -g foo.c

它的目标是’foo.o’,依赖于’foo.c’和’defs.h’,有一个命令’cc –c –g foo.c’。命令行以TAB字符开始标识它是一个命令。

这条规则说明两件事:

8.如何决定’foo.o’是旧的:如果它不存在,或者’foo.c’或者’defs.h’比它新。

9.如何更新’foo.o’文件:通过运行’cc’程序。命令未提及’defs.h’,担可以猜想’foo.c’包含了它,这是’defs.h’被置于依赖关系中的理由。

3.2.规则的语法

语法如下:

TARGETS : DEPENDENCIES

COMMAND

...

或者

TARGETS : DEPENDENCIES ; COMMAND

COMMAND

...

TARGETS是以空格隔开的文件名,统配符可以使用。通常一个规则只有一个目标,偶尔也有多个。

命令行以TAB键开始。第一条命令可在依赖关系的下一行;或者在同一行,在分号后面;两种方式效果相同。

因为’$’符号被用做变量引用,如果要在规则中使用’$’符号,必须写两个:’$$’。可以用’’符号来分割一个长行,这不是必须的,因为make对行的

长度没有限制。

3.3.通配符

规则中的文件名可以包含统配符,如’*’,’?’。

文件名前的字符’~’有特殊的含义。单独使用,或跟随一个’/’,代表用户的home目录,比如’~/bin’扩展为/home/you/bin’;如果’~’跟随一个单词,

表示单词指示的那个用户的home目录,如’~john/bin’扩展为’/home/john/bin’。

通配符在目标,依赖关系,命令中自动扩展,其它情况下,统配符的扩展除非显式使用’wildcard’函数。通配符的特殊意义可以使用’’符号关闭。

例子:

clean:

rm -f *.o

print: *.c

lpr -p $?

touch print

通配符在定义变量时并不扩展,例如:

objects = *.o

则objects的值是字符串’*.o’;但是如果你将objects用于目标,依赖或命令中,扩展会进行。要将objects设置成扩展过的内容,使用:

objects := $(wildcard *.o)

3.3.1.通配符的缺陷

这是一个使用通配符的例子,但结果不是你所期望的。假设可执行文件’foo’是从当前目录中的所有’.o’文件生成的:

objects = *.o

foo : $(objects)

cc -o foo $(CFLAGS) $(objects)

objects变量的值是字符串’*.o’。通配符扩展在规则’foo’中进行,于是所有存在的’.o’文件成为’foo’的依赖而且在需要时重新编译。

但如果删除了所有的’.o’文件呢?当通配符不匹配任何文件时,一切都保持原样:则’foo’依赖于一个叫做’*.o’的文件;由于这个文件不大可能存在,

’make’程序会报告一个无法生成’*.o’文件的错误,这不是期待的结果。

实际上可以用通配符获得期望结果,但是需要复杂的技术,包括’wildcard’函数和字符串替换函数。

3.3.2.wildcard函数

通配符自动在规则中进行。但是在变量赋值的和函数的参数中通配符不会扩展,如果在这些情况下需要通配符扩展,必须使用’wildcard’函数。

语法如下:

$(wildcard PATTERN...)

这个在makefile任何地方出现的字符串,会被匹配任何一个文件名格式的以空格隔开的现有文件列表替换。如果没有任何文件匹配一个模式,

这个模式从’wildcard’的输出中忽略,注意,这和上述的通配符的处理是不一样的。

‘wildcard’函数的一个功能是找出目录中所有的’.c’文件:

$(wildcard *.c)

可以通过替换后缀’.c’为’.o’从C文件列表得到目标文件的列表:

$(patsubst %.c,%.o,$(wildcard *.c))

这样,上节中的makefile改写为:

objects := $(patsubst %.c,%.o,$(wildcard *.c))

foo : $(objects)

cc -o foo $(objects)

这个makefile利用了编译C程序的隐含规则,所以不需要对编译写出显式的规则。(’:=’是’=’的一个变体)

注意:’PATTERN’是大小写敏感的。

3.4.目录搜索

对于大的系统,通常将源文件和目标文件放在不同的目录中。目录搜索功能可以让make自动在多个目录中搜寻依赖文件,当你将文件重新分布是,

不需要改变规则,更改搜索路径即可。

3.4.1.‘VPATH’

make变量’VPATH’列出make应当搜索的目录列表。很多情况下,当前目录不包含依赖文件,’VPATH’描述一个对所有文件的搜索列表,包含那些

是规则的目标的文件。

如果一个目标或者依赖文件在当前目录没找到的话,’make’在’VPATH’中列出的目录中查找同名的文件。如果找到的话,那个文件成为依赖文件;

规则可以象这些文件在当前目录中一样来使用他们。

在’VPATH’变量中,目录名以冒号或空格隔开;目录列出的顺序决定make查找的顺序。(注:在pSOSystem 2.5移植到Win32的GNU make目录

名必须使用分号隔开,以下均简称Win32 GNU make)。举例说明:

VPATH = src:../headers 则规则

foo.o : foo.c

被解释为

foo.o : src/foo.c

假设’foo.c’在当前目录不存在,在’src’目录中可以找到。

3.4.2.选择性搜索

与’VPATH’变量相似但更具选择性的是’vpath’指令(注意是小写),可以指定对于符合特定模式文件的查找路径。这样可以为不同类型的文件指定不同的搜索路径。

‘vpath’指令共有三中形式:

  • ‘vpath PATTERN DIRECTORIES’  为匹配PATTERN的文件名指定搜索路径DIRECTORIES,目录的分隔和’VPATH’的相同
  • ‘vpath PATTERN’  清除为匹配PATTERN的文件名指定的搜索路径
  • ‘vpath’   清除所有以前用’vpath’指定的搜索路径

‘vpath’的模式是包含’%’的字符串:这个字符串必须匹配需要搜索的依赖文件名,’%’字符匹配0个或多个任意字符。例如:’%.h’匹配任何以’.h’结尾的文件(如果没有%,则PATTERN必须和依赖文件完全一致,这种用法不太多)。

当当前目录中不存在依赖文件时,如果’vpath’中的PATTERN匹配依赖文件名,则指令中DIRECTORIES列出的目录和’VPATH’中同样处理。举例:

vpath %.h ../headers

告诉make在当前目录中未找到的’.h’文件在../headers目录中查找。

如果多个’vapth’的模式匹配依赖文件名,make将逐一处理,在所有指定的目录中搜索。Make按照’vapth’在makefile中的次序;来处理它们,多个相同模式的’vapth’是相互独立的。

vpath %.c foo

vpath % blish

vpath %.c bar

将按照’foo’,‘blish’,’bar’的次序查找’.c’文件。而

vpath %.c foo:bar

vpath % blish

按照’foo’,’bar’,’blish’的顺序搜索。

3.4.3.使用自动变量

目录搜索的结果并不改变规则中的命令:命令按原样被执行。因此,必须写出与目录搜索功相适应的命令。这可以通过使用’$^’这样的自动变量来

完成。’$^’表示规则中的所有依赖文件,包含它们所在的目录名(参见目录搜索);’$@’表示目标。例如:

foo.o : foo.c

cc -c $(CFLAGS) $^ -o $@

通常情况下,依赖文件也包含头文件,但命令中并不提及这些文件:变量’$<’表示第一个依赖文件:

VPATH = src:../headers

foo.o : foo.c defs.h hack.h

cc –c $(CFLAGS) $< -o $@

3.4.4.目录搜索和隐含规则

使用’VPATH’和’vpath’指定目录搜索也会影响隐含规则。例如:文件’foo.o’没有显式规则,make会考虑隐式规则:如果’foo.c’存在则编译它;如果这个文件不存在,则在相应的目录中查找;如果’foo.c’在任一的目录中存在,则C编译的隐式规则被应用。

隐式规则的命令使用自动变量通常是必要的,这样无需其它努力即可以使用目录搜索得到的文件名。

3.5.PHONY目标

Phony目标并非实际的文件名:只是在显式请求时执行命令的名字。有两种理由需要使用phony目标:避免和同名文件冲突,改善性能。

如果编写一个规则,并不产生目标文件,则其命令在每次make该目标时都执行。例如:

clean:

rm *.o temp

因为’rm’命令并不产生’clean’文件,则每次执行’make clean’的时候,该命令都会执行。如果目录中出现了’clean’文件,则规则失效了:没有依赖文件,文件’clean’始终是最新的,命令永远不会执行;为避免这个问题,可使用’.PHONY’指明该目标。如:

.PHONY : clean

这样执行’make clean’会无视’clean’文件存在与否。

已知phony目标并非是由其它文件生成的实际文件,make会跳过隐含规则搜索。这就是声明phony目标会改善性能的原因,即使你并不担心实际文件存在与否。完整的例子如下:

.PHONY : clean

clean :

rm *.o temp

phony目标不应是真正目标文件的依赖。如果这样,每次make在更新此文件时,命令都会执行。只要phony目标不是真正目标的依赖,规则的命令只有在指定此目标时才执行。

Phony目标可以有依赖关系。当一个目录中有多个程序是,将其放在一个makefile中会更方便。因为缺省目标是makefile中的第一个目标,通常将这个phony目标叫做’all’,其依赖文件为各个程序:

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

这样,使用’make’将可以将三个程序都生成了。

当一个phony目标是另一个的依赖,其作用相当于子程序,例如:

.PHONY: cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff

rm program

cleanobj :

rm *.o

cleandiff :

rm *.diff

3.6.FORCE目标

当规则没有依赖关系也没有命令,而且其目标不是存在的文件名,make认为此规则运行时这个目标总是被更新。这意味着如果规则依赖于此目标,其命令总是被执行。

clean: FORCE

rm $(objects)

FORCE:

例中目标’FORCE’满足这种特殊条件,这样依赖于它的目标’clean’被强制执行其命令。名字’FORCE’没有特殊含义,只不过通常这样用而已。这种方式使用’FORCE’和’.PHONY : clean’效果相同。使用’.PHONY’更加明确高效,担不是所有的’make’都支持;这样许多makefile中使用了’FORCE’。

3.7.空目标

空目标(empty target)是phony目标的变种:用来执行显式请求的一个动作。和phony目标不同的是:这个目标文件可以真实存在,担文件的内容无关紧要,通常是空的。空目标文件的目的是利用其最后修改时间来记录命令最近一次执行的时间,这是通过使用’touch’命令更新目标文件来达到的。

print: foo.c bar.c

lpr -p $?

touch print

利用这条规则,执行’make print’时如果自上次’make print’之后任一文件改变了,’lpr’命令会执行。自动变量’$?’是为了只打印出那些变化了的文件。

3.8.内建的特殊目标

某些名字作为目标存在时有特殊含义。

PHONY 该目标的依赖被认为是phony目标,处理这些目标时,命令无条件被执行,不管文件名是否存在及其最后修改时间

SUFFIXES 该目标的依赖被认为是一个后缀列表,在检查后缀规则时使用

DEFAULT 该目标的规则被使用在没有规则(显式的或隐含的)的目标上。如果’DEFAULT’命令定义了,则对所有不是规则目标的依赖文件都会执行该组命令

PRECIOUS 该目标的依赖文件会受到特别对待:如果make被kill或命令的执行被中止,这些目标并不删除;而且如果该目标是中间文件,在不需要时不会被删除。可以将隐含规则的目标模式(如%.o)做为’.PRECIOUS’的依赖文件,这样可以保存这些规则产生的中间文件。

INTERMEDIATE 该目标的依赖文件被当作中间文件;如果该目标没有依赖文件,则makefile中所有的目标文件均被认为是中间文件。

IGNORE 在执行该目标的依赖规则的命令时,make会忽略错误,此规则本身的命令没有意义。如果该规则没有依赖关系,表示忽略所有命令执行的错误,这种用法只是为了向后兼容;由于会影响到所有的命令,所以不是特别有用,推荐使用其它更有选择性忽略错误的方法。

SILENT 在执行该目标的依赖规则的命令时,make并不打印命令本身。该规则的命令没有意义。在’.SILIENT’没有依赖关系时,表示执行makefile中的所有命令都不会打印,该规则只是为了向后兼容提供的。

EXPORT_ALL_VARIABLES 只是作为一个目标存在,指示make将所有变量输出到子进程中。

定义的隐含规则的后缀作为目标时,也认为它是特殊目标;两个后缀的连接也是一样,比如’.c.o’。这些目标是后缀规则,一中定义隐式规则的过时方法(但仍然广泛使用)。后缀通常以’.’开始,所以特殊目标也以’.’开始。

3.9.一个规则多个目标

一条有多个目标的规则和写多条规则,每条一个目标作用是等同的。同样的命令应用于所有目标,但其效用会因将实际目标以’$@’代替而不同。规则中所有目标的依赖关系是一样的。

这在两种情况下有用:

★只有依赖关系,不需要命令。例如:

kbd.o command.o files.o: command.h

所有的目标同样的命令。命令不需要完全相同,因为在命令中可以使用

’$@’:

bigoutput littleoutput : text.g

generate text.g -$(subst output,,$@) > $@

bigoutput : text.g

generate text.g -big > bigoutput

littleoutput : text.g

generate text.g -little > littleoutput等同。这里假设程序’generate’产生两种输出:一种使用’-big’选项,一种使用’-little’选项。如果想象使用’$@’变化命令那样来变化依赖关系,不能通过多目标的普通规则实现,但是可以通过模式规则来实现。

3.10.一个目标多条规则

一个文件可以是多条规则的目标,所有规则的依赖关系被合并。如果目标比任一个依赖文件旧,命令被执行。

一个文件只能有一组命令执行。如果多个规则对于同一个文件都给出了命令,make使用最后一组并打印错误信息(特殊情况:如果文件名以’.’开始,并不打印错误信息,这一点是为了和其它make兼容)。没有任何理由需要将makefile写成这样,这是make给出错误信息的理由。

一条只有依赖关系的附加规则可以一次给出许多文件的附加依赖文件。例如’objects’变量表示系统中编译器的所有输出.,说明当’config.h’更改时所有文件必须重做的简单方法如下:

objects = foo.o bar.o

foo.o : defs.h

bar.o : defs.h test.h

$(objects) : config.h

不用改变实际目标文件生成的规则,这条规则可以在需要增删附加的依赖关系时插入或提出。另一个诀窍是附加的依赖关系可以用变量表示,

在make执行时,可以给变量赋值:

extradeps=

$(objects) : $(extradeps)

当命令`make extradeps=foo.h'执行时会认为’foo.h’是每个目标文件的依赖文件,但简单的’make’命令不是这样。

3.11.静态模式规则

静态模式规则(static pattern rules)可以指定多个目标,并且使用目标名字来建议依赖文件的名字;比普通多目标规则更通用因为不需要依赖关系是相同的:依赖关系必须类似但不需要相同。

3.11.1.语法

TARGETS ...: TARGET-PATTERN: DEP-PATTERNS ...

COMMANDS

...

TARGETS列表指出规则应用的目标,可以包含通配符,于普通规则的目标相同。TARGET-PATTERN和DEP-PATTERNS来表明目标的依赖关系如何计算:匹配TARGET-PATTERN的目标从名字中抽出一部分,叫做词干(stem),词干被替换到DEP-PATTERNS来形成依赖文件名。

每个模式通常包含一个’%’字符。当TARGET-PATTERN匹配一个目标时,’%’字符可以匹配目标名中的任何部分;这部分即是词干,模式的其余部分必须完全匹配。例如’foo.o’匹配’%.o’,’foo’是词干;目标’foo.c’和’foo.out’并不匹配这个模式。

目标的依赖文件名通过将DEP-PATTERNS中的’%’替换为词干形成:如果依赖模式为’%.c’,在替换词干’foo’可以得到’foo.c’。依赖模式中不包含’%’也是合法的,此依赖文件对所有的目标均有效。

如果需要在模式规则中使用’%’字符,必须在其前面加’’字符,如果’%’前的’’字符是有实际意义的,必须在其前面加’’,其它的’’不必如此处理。如’the\%weird\%pattern’在有效的’%’前是’the%weird’,其后是’pattern’。最后的’’保持原样是因为其并不影响’%’字符。

以下例子从相应的’.c’文件编译’foo.o’和’bar.o’:

objects = foo.o bar.o

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

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

每个目标必须匹配目标模式,对于不匹配的目标会给出警告。如果列表中只有部分文件匹配模式,可以使用filter函数移去不匹配的文件名:

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c

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

$(filter %.elc,$(files)): %.elc: %.el

emacs -f batch-byte-compile $<

例子中`$(filter %.o,$(files))' 结果是`bar.o lose.o’; `$(filter %.elc,$(files))' 的结果是`foo.elc'。以下例子说明’$*’的使用:

bigoutput littleoutput : %output : text.g

generate text.g -$* > $@

命令`generate'执行时,’$*’扩展为词干’big’或’little’。

3.11.2.静态模式规则和隐式规则

静态模式规则和隐式规则在作为模式规则是具有很多共同点,都有目标模式和构造依赖文件名的模式,不同之处在于make决定何时应用规则的方法。

隐式规则可应用于匹配其模式的任何目标,但只限于没有指定命令的目标,如果有多条可应用的隐式规则,只有一条被使用,取决于规则的顺序。反之,静态模式规则适用于规则中明确目标列表,不适用于其它目标且总是适用于指定的每个目标。如果有两条冲突的规则,且都有命令,这是一个错误。

静态模式规则比隐式规则优越之处如下:

可为一些不能按句法分类,但可以显式列出的文件重载隐式规则

不能判定目录中的精确内容,一些无关的文件可能导致make适用错误的隐式规则;最终结果可能依赖于隐式规则的次序。适用静态模式规则时,这种不确定性是不存在的:规则适用于明确指定的目标。

3.12.双冒号规则

双冒号规则(Double-colon rules)的目标后是’::’而不是’:’,当一个目标出现在多条规则中时,其处理和普通规则的处理不同。

当一个目标出现在多条规则中时,所有规则必须是相同类型的:都是普通的或者都是双冒号的。如果是双冒号,规则之间相互独立;如果目标需要更新,则规则的命令被执行;结果可能是没有执行,或者执行了其中一些,或者所有的规则都执行了。

同一目标的双冒号规则事实是完全孤立的,每条规则被被单独处理,就象不同目标的规则一样;规则按照在makefile中出现的次序被处理,此类规则真正有意义的是那些于命令执行次序无关的。

这种规则有时比较晦涩不是特别有用;它提供了一种机制:通过不同依赖文件的更新来对目标进行不同的处理,这种情形很罕见。每个这种规则应当提供命令,如果没有,适用的隐式规则将使用。

3.13.自动生成依赖关系

在makefile中,许多规则都是一些目标文件依赖于一些头文件。例如:’main.c’ 通过’#include’使用’defs.h’,这样规则:

main.o: defs.h

告诉make在’defs.h’变化时更新’main.o’。在程序比较大时,需要写许多这样的规则;而且当每次增删’#include’时,必须小心的更新makefile。许多现代的编译器可以帮你写这些规则,通常这是通过编译器的’-M’选项,例如命令:

cc –M main.c

输出以下内容:

main.o : main.c defs.h

这样就不必写这些规则,有编译器代劳了。

注意这样的依赖关系中提及’main.o’,不会被隐式规则认为是中间文件,这意味这make在使用过它之后不会将其删除。使用老的’make’程序时,习惯做法是使用’make depend’命令利用编译器的功能产生依赖关系,该命令会产生一个’depend’文件包含所有自动产生的依赖关系,然后在makefile中

使用’include’将其读入。

使用GNU的make时,重新生成makefile的功能使得这种做法变得过时:从不需要显式请求更新依赖关系,因为它总是重新生成任何过时的makefile。

自动依赖关系生成推荐的做法是对每个源文件做一个makefile。对每个源文件’NAME.c’,有一个makefile ’NAME.d’,其中列出了目标文件’NAME.o’依赖的所有文件,这样在源文件更新时,需要扫描来产生新的依赖关系。例子是一个从’NAME.c’产生依赖关系文件’NAME.d’的模式规则:

%.d: %.c

$(SHELL) -ec '$(CC) -M $(CPPFLAGS) $<

| sed '''s/($*).o[ :]*/1 $@/g''' > $@'

-e选项是当$(CC)命令失败时(exit状态非0),shell立刻退出。通常shell的返回值是管道中最后一条命令(sed)的返回值,这样make不会注意到编译器出错。

使用GNU的C编译器时(gcc),可以用’-MM’选项来代替’-M’选项,这样省略系统头文件的依赖关系。’sed’命令的目的是将

main.o : main.c defs.h

转换为

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

这样使得每个’.d’文件依赖于’.o’文件相应源文件和头文件,make则可以在原文间或头文件变化时更新依赖关系文件。

如果定义了生成’.d’文件的规则,可以使用’include’指令来读入所有的文件:

sources = foo.c bar.c

include $(sources:.c=.d)

例中使用替换变量来将源文件列表’ foo.c bar.c’转换为依赖关系文件的列表。因为’.d’文件和其它文件一样,不需要更多工作,make会在需要时重新生成它们。

四、编写命令

规则的命令是由一一执行的shell命令组成。除了以分号隔开写在依赖关系后的命令,每个命令行必须以tab字符开始空行和注释行可以出现在命令行中,处理时被忽略(注意:以tab字符开始的空行不是’空’行,是一条空命令)。可以在命令中使用任何程序,但这些程序是由$(SHELL)来执行的。

4.1.回显

通常make打印出要执行的命令,称之为回显,这和亲自敲命令的现象是一样的。当行之前有’@’字符时,命令不再回显,字符’@’在传递给shell前丢弃。

典型的用法是只对打印命令有效,比如’echo’命令:

@echo About to make distribution files

当make使用’-n’或’—just-print’选项时,显示要发生的一切,但不执行命令。只有在这种情况下,即使命令以’@’开始,命令行仍然显示出来。

这个选项对查看make实际要执行的动作很有用。

‘-s’或’—silent’选项阻止make所有回显,就象所有命令以’@’开始一样;一条没有依赖关系的’.SILENT’规则有相同的作用,但是’@’更加灵活。

4.2.执行

在需要执行命令更新目标时,make为每一行创建一个子shell来执行。这意味着诸如为进程设置局部变量的shell命令’cd’(改变进程的当前目录)不会影响以后的命令。如果需要’cd’影响下一个命令,将它们放在一行上用分号隔开,这样make认为是一条命令传递给shell程序(注意:这需要shell支持):

foo : bar/lose

cd bar; gobble lose > ../foo

另一个形式使用续行符:

foo : bar/lose

cd bar;

gobble lose > ../foo

shell程序的名字是通过’SHELL’变量来取得的。

(*UNIX)不象大多数变量,’SHELL’变量不是通过环境来设置的(即需要在makefile中设置),因为’SHELL’环境是个人选择的,如果不同人的选择会影响makefile的功能的话,这样很糟糕。

4.3.并行执行

GNU make可以一次执行几条命令。通常make一次执行一条命令,等待其返回,再执行下一条。使用’-j’或’—jobs’可以同时执行多条命令。如果’-j’后梗一个正数,表示一次可以执行的命令条数;如果’-j’之后没有参数,则不限制可执行的命令数。缺省的数量是一。

一个讨厌的问题是如果同时执行多条命令,它们的输出会混在一起;另一个问题是两个进程不能从同一个设备获得输入。

4.4.错误

每条shell命令返回时,make会检查其返回状态。如果命令执行成功,则下一条命令被执行,最后一条命令执行完后,规则执行结束。

如果有错误(返回非0状态),make放弃当前规则,也可能是所有规则。

有时候命令执行错误并不是问题,比如使用’mkdir’命令确保目录存在:如果目录一存在,则’mkdir’会报告错误,但仍希望make继续。

要忽略命令的错误,在命令之前使用’-‘字符,’-‘字符在传递给shell之前被丢弃:

clean:

-rm -f *.o

如果使用’-i’或’—ignore-errors’选项,make会忽略所有命令产生的错误;一条没有依赖关系的’.IGNORE’规则有相同的作用,但’-‘更灵活。

在忽略错误时,make将错误也认为是成功,只是通知你命令的退出状态和和错误被忽略。如果make并未告知忽略错误,在错误发生时,表明该目标不能成功更新,直接或间接依赖于此的目标当然也不能成功;这些目标的命令不会被执行,因为其先决条件不满足。

通常make会立即以非0状态退出。然而,如果给定’-k’或’—keep-going’选项,make在退出前会处理其它的依赖关系,进行必要的更新。例如,在编译一个目标文件遇到错误,’make -k’会继续编译其它的目标文件。

通常认为你的目的是更新指定的目标,当make知道这是不可能时,会立即报告失败;’-k’选项指示真正目的是测试更新程序的更多可能性:在编译之前找出更多不相关的问题。

如果命令失败了,假设它更新的目标文件,这个文件是不完整的不能使用-至少不是完全更新的。但文件的最后修改时间表明停已经是最新的,下一次make运行时,不会再更新这个文件。这种情况和命令被kill相同;则通常情况下在命令失败时将目标删除是正确的;当’.DELETE_ON_ERROR’是目标时make帮你做这件事。虽然你总是希望make这么做,但这不是过去的习惯;所以必须显式要求make这样做(其它的make自动这样做)。

4.5.中断make

如果make执行命令时遇到错误,可能会删除命令更新的目标文件: make检查文件的修改时间是否变化。删除目标的目的是确保make下次执行时重新生成它。为什么这样做?假设在编译器运行时按了’Ctrl-c’,此时编译器写生成目标文件’foo.o’。’Ctrl-c’ kill了编译器,留下一个不完整的文件,但它的修改时间比源文件’foo.c’新;此时make也受到’Ctrl-c’信号删除这个不完整的文件,如果make不这样做,下次make运行时认为’foo.o’不需要更新,会在链接时出现奇怪的错误。

可以使用’.PRECIOUS’规则来防止目标文件被删除。在make更新目标时,会检测其是否为’.PRECIOUS’的依赖,决定在命令出错或中断时是否删除该目标。如果你希望目标的更新是原子操作,或是用来记录修改时间,或必须一直存在防止其它类型的错误,这些理由使得你必须这样做。

4.6.递归使用

递归使用make就是在makefile中使用make命令。这种技术在你将一个大系统分解为几个子系统,为每个自系统提供一个makefile时有用处。比如有一个子目录’subdir’中有自己的makefile,希望make在自目录中运行,可以这样做:

subsystem:

cd subdir; $(MAKE)

或者

subsystem:

$(MAKE) -C subdir

可以照抄这个;例子来递归使用make

4.6.1.‘MAKE’变量

递归的make必须使用’MAKE’变量,不是显式的make命令:

subsystem:

cd subdir; $(MAKE)

该变量的值是被调用的make的名字。在命令中使用’MAKE’有特殊的功能:它改变了`-t' (`--touch'), `-n' (`--just-print')和`-q' (`--question')选项的含义。使用上例来考虑’make –t’命令(’-t’选项将目标标记为最新但不运行命令),更加’-t’选项的功能,该命令将创建一个’subsystem’文件,实际希望的操作是运行’cd subdir; make –t’;但这会执行命令,与’-t’的原意不符。

这个特殊功能做了期望的工作。当命令行包含变量’MAKE’时,选项’-t’,’-n’和’-q’并不适用。不管这些导致不会执行命令的标志,包含’MAKE’变量的命令始终会执行。正常的’MAKEFLAGS’机制将这些标志传递到子make,这样打印命令的请求被传播到子系统中。

4.6.2.传递变量到子make

上级(top-level)make中的变量可以显式通过环境传递到子make中。在子make中,这些变量被缺省定义,但不会重载子makefile中的定义除非使用’-e’选项。为向下传递,或输出变量,make在运行命令时将其加入到环境变量中;子make,可以使用环境变量来初始化变量表。除非显式要求,make只输出初始环境中或命令行设置的变量而且变量名只由字母,数字和下划线组成。一些shell不能处理有其它字符的环境变量。

特殊变量’SHELL’,’MAKEFLAGS’总是输出,如果’MAKEFILE’变量有值,也会输出。Make自动通过’MAKEFLAGS’来输出命令行定义的变量。

如果想要输出特定变量,使用’export’指令:

export VARIABLE ...

如果要阻止输出一个变量,使用’unexport’指令:

unexport VARIABLE ...

为方便起见,可以在定义变量时输出它:

export VARIABLE = value

VARIABLE = value

export VARIABLE

作用相同。

如果要输出所有的变量,使用’export’指令本身就可以了。

变量’MAKELEVEL’在一级一级传递时会改变,这个变量的值是表示嵌套层数的字符串,顶级’make’是,变量的值为’0’;子make的值为’1’;子子make的值为’2’,依此类推。

‘MAKELEVEL’的用途是在条件指令中测试它,这样写出在递归运行时和直接运行时表现不同的makefile。

附:

以下内容拷贝自GNU Make Manual

 

命令行参数

`-b'

`-m'

These options are ignored for compatibility with other versions of

`make'.

`-C DIR'

`--directory=DIR'

Change to directory DIR before reading the makefiles. If multiple

`-C' options are specified, each is interpreted relative to the

previous one: `-C / -C etc' is equivalent to `-C /etc'. This is

typically used with recursive invocations of `make' (*note

Recursive Use of `make': Recursion.).

`-d'

`--debug'

Print debugging information in addition to normal processing. The

debugging information says which files are being considered for

remaking, which file-times are being compared and with what

results, which files actually need to be remade, which implicit

rules are considered and which are applied--everything interesting

about how `make' decides what to do.

`-e'

`--environment-overrides'

Give variables taken from the environment precedence over

variables from makefiles. *Note Variables from the Environment:

Environment.

`-f FILE'

`--file=FILE'

`--makefile=FILE'

Read the file named FILE as a makefile.

`-h'

`--help'

Remind you of the options that `make' understands and then exit.

`-i'

`--ignore-errors'

Ignore all errors in commands executed to remake files.

`-I DIR'

`--include-dir=DIR'

Specifies a directory DIR to search for included makefiles. If several `-I' options are

used to specify several directories, the directories are searched

in the order specified.

`-j [JOBS]'

`--jobs=[JOBS]'

Specifies the number of jobs (commands) to run simultaneously.

With no argument, `make' runs as many jobs simultaneously as

possible. If there is more than one `-j' option, the last one is

effective.

`-k'

`--keep-going'

Continue as much as possible after an error. While the target that

failed, and those that depend on it, cannot be remade, the other

dependencies of these targets can be processed all the same.

`-l [LOAD]'

`--load-average[=LOAD]'

`--max-load[=LOAD]'

Specifies that no new jobs (commands) should be started if there

are other jobs running and the load average is at least LOAD (a

floating-point number). With no argument, removes a previous load

limit. *Note Parallel Execution: Parallel.

`-n'

`--just-print'

`--dry-run'

`--recon'

Print the commands that would be executed, but do not execute them.

`-o FILE'

`--old-file=FILE'

`--assume-old=FILE'

Do not remake the file FILE even if it is older than its

dependencies, and do not remake anything on account of changes in

FILE. Essentially the file is treated as very old and its rules

are ignored.

`-p'

`--print-data-base'

Print the data base (rules and variable values) that results from

reading the makefiles; then execute as usual or as otherwise

specified. This also prints the version information given by the

`-v' switch (see below). To print the data base without trying to

remake any files, use `make -p -f /dev/null'.

`-q'

`--question'

"Question mode". Do not run any commands, or print anything; just

return an exit status that is zero if the specified targets are

already up to date, one if any remaking is required, or two if an

error is encountered.

`-r'

`--no-builtin-rules'

Eliminate use of the built-in implicit rules.You can still define your own by writing

pattern rules. The `-r' option also clears out the default list

of suffixes for suffix rules .But you can still define your own suffixes with a

rule for `.SUFFIXES', and then define your own suffix rules.

`-s'

`--silent'

`--quiet'

Silent operation; do not print the commands as they are executed.

`-S'

`--no-keep-going'

`--stop'

Cancel the effect of the `-k' option. This is never necessary

except in a recursive `make' where `-k' might be inherited from

the top-level `make' via `MAKEFLAGS' or if you set `-k' in `MAKEFLAGS' in your

environment.

`-t'

`--touch'

Touch files (mark them up to date without really changing them)

instead of running their commands. This is used to pretend that

the commands were done, in order to fool future invocations of

`make'.

`-v'

`--version'

Print the version of the `make' program plus a copyright, a list

of authors, and a notice that there is no warranty; then exit.

`-w'

`--print-directory'

Print a message containing the working directory both before and

after executing the makefile. This may be useful for tracking

down errors from complicated nests of recursive `make' commands.

`--no-print-directory'

Disable printing of the working directory under `-w'. This option

is useful when `-w' is turned on automatically, but you do not

want to see the extra messages.

`-W FILE'

`--what-if=FILE'

`--new-file=FILE'

`--assume-new=FILE'

Pretend that the target FILE has just been modified. When used

with the `-n' flag, this shows you what would happen if you were

to modify that file. Without `-n', it is almost the same as

running a `touch' command on the given file before running `make',

except that the modification time is changed only in the

imagination of `make'.

`--warn-undefined-variables'

Issue a warning message whenever `make' sees a reference to an

undefined variable. This can be helpful when you are trying to

debug makefiles which use variables in complex ways.

 

指令

`define VARIABLE'

`endef'

Define a multi-line, recursively-expanded variable.

*Note Sequences::.

`ifdef VARIABLE'

`ifndef VARIABLE'

`ifeq (A,B)'

`ifeq "A" "B"'

`ifeq 'A' 'B''

`ifneq (A,B)'

`ifneq "A" "B"'

`ifneq 'A' 'B''

`else'

`endif'

Conditionally evaluate part of the makefile.

`include FILE'

Include another makefile.

`override VARIABLE = VALUE'

`override VARIABLE := VALUE'

`override VARIABLE += VALUE'

`override define VARIABLE'

`endef'

Define a variable, overriding any previous definition, even one

from the command line.

`export'

Tell `make' to export all variables to child processes by default.

`export VARIABLE'

`export VARIABLE = VALUE'

`export VARIABLE := VALUE'

`export VARIABLE += VALUE'

`unexport VARIABLE'

Tell `make' whether or not to export a particular variable to child

processes.

`vpath PATTERN PATH'

Specify a search path for files matching a `%' pattern.

*Note The `vpath' Directive: Selective Search.

`vpath PATTERN'

Remove all search paths previously specified for PATTERN.

`vpath'

Remove all search paths previously specified in any `vpath'

directive.

 

函数

`$(subst FROM,TO,TEXT)'

Replace FROM with TO in TEXT.

`$(patsubst PATTERN,REPLACEMENT,TEXT)'

Replace words matching PATTERN with REPLACEMENT in TEXT.

`$(strip STRING)'

Remove excess whitespace characters from STRING.

`$(findstring FIND,TEXT)'

Locate FIND in TEXT.

`$(filter PATTERN...,TEXT)'

Select words in TEXT that match one of the PATTERN words.

`$(filter-out PATTERN...,TEXT)'

Select words in TEXT that *do not* match any of the PATTERN words.

`$(sort LIST)'

Sort the words in LIST lexicographically, removing duplicates.

`$(dir NAMES...)'

Extract the directory part of each file name.

`$(notdir NAMES...)'

Extract the non-directory part of each file name.

`$(suffix NAMES...)'

Extract the suffix (the last `.' and following characters) of each

file name.

`$(basename NAMES...)'

Extract the base name (name without suffix) of each file name.

`$(addsuffix SUFFIX,NAMES...)'

Append SUFFIX to each word in NAMES.

`$(addprefix PREFIX,NAMES...)'

Prepend PREFIX to each word in NAMES.

`$(join LIST1,LIST2)'

Join two parallel lists of words.

`$(word N,TEXT)'

Extract the Nth word (one-origin) of TEXT.

`$(words TEXT)'

Count the number of words in TEXT.

`$(firstword NAMES...)'

Extract the first word of NAMES.

`$(wildcard PATTERN...)'

Find file names matching a shell file name pattern (*not* a `%'

pattern).

`$(shell COMMAND)'

Execute a shell command and return its output.

`$(origin VARIABLE)'

Return a string describing how the `make' variable VARIABLE was

defined.

`$(foreach VAR,WORDS,TEXT)'

Evaluate TEXT with VAR bound to each word in WORDS, and

concatenate the results.

 

 

自动变量

`$@'

The file name of the target.

`$%'

The target member name, when the target is an archive member.

`$<'

The name of the first dependency.

`$?'

The names of all the dependencies that are newer than the target,

with spaces between them. For dependencies which are archive

members, only the member named is used

with spaces between them. For dependencies which are archive

members, only the member named is used

`$^'

`$+'

The names of all the dependencies, with spaces between them. For

dependencies which are archive members, only the member named is

used. The value of `$^' omits duplicate

dependencies, while `$+' retains them and preserves their order.

`$*'

The stem with which an implicit rule matches

`$(@D)'

`$(@F)'

The directory part and the file-within-directory part of `$@'.

`$(*D)'

`$(*F)'

The directory part and the file-within-directory part of `$*'.

`$(%D)'

`$(%F)'

The directory part and the file-within-directory part of `$%'

`$(<D)'

`$(<F)'

The directory part and the file-within-directory part of `$<'

`$(^D)'

`$(^F)'

The directory part and the file-within-directory part of `$^'

`$(+D)'

`$(+F)'

The directory part and the file-within-directory part of `$+'

`$(?D)'

`$(?F)'

The directory part and the file-within-directory part of `$?'

 

特殊变量

`MAKEFILES'

Makefiles to be read on every invocation of `make'.

`VPATH'

Directory search path for files not found in the current directory.

`SHELL'

The name of the system default command interpreter, usually

`/bin/sh'. You can set `SHELL' in the makefile to change the

shell used to run commands.

`MAKE'

The name with which `make' was invoked. Using this variable in

commands has special meaning.

`MAKELEVEL'

The number of levels of recursion (sub-`make's).

`MAKEFLAGS'

The flags given to `make'. You can set this in the environment or

a makefile to set flags.

`SUFFIXES'

The default list of suffixes before `make' reads any makefiles.

Make命令完全详解教程的更多相关文章

  1. PHP cURL实现模拟登录与采集使用方法详解教程

    来源:http://www.zjmainstay.cn/php-curl 本文将通过案例,整合浏览器工具与PHP程序,教你如何让数据 唾手可得 . 对于做过数据采集的人来说,cURL一定不会陌生.虽然 ...

  2. 【python3+request】python3+requests接口自动化测试框架实例详解教程

    转自:https://my.oschina.net/u/3041656/blog/820023 [python3+request]python3+requests接口自动化测试框架实例详解教程 前段时 ...

  3. 【转】在VMware中为Linux系统安装VM-Tools的详解教程

    在VMware中为Linux系统安装VM-Tools的详解教程 如果大家打算在VMware虚拟机中安装Linux的话,那么在完成Linux的安装后,如果没有安装Vm-Tools的话,一部分功能将得不到 ...

  4. 【山外笔记-数据库】Memcached详解教程

    本文打印版文档下载地址 [山外笔记-数据库]Memcached详解教程-打印版.pdf 一.Memcached数据库概述 1.Memcached简介 (1)Memcached是一个自由开源的,高性能, ...

  5. Iperf3网络性能测试工具详解教程

    Iperf3网络性能测试工具详解教程 小M 2020年4月17日 运维 本文下载链接 [学习笔记]Iperf3网络性能测试工具.pdf 网络性能评估主要是监测网络带宽的使用率,将网络带宽利用最大化是保 ...

  6. 【山外笔记-工具框架】iperf3网络性能测试工具详解教程

    [山外笔记-工具框架]iperf3网络性能测试工具详解教程   本文下载链接 [学习笔记]iperf3网络性能测试工具.pdf 网络性能评估主要是监测网络带宽的使用率,将网络带宽利用最大化是保证网络性 ...

  7. 《手把手教你》系列基础篇(八十七)-java+ selenium自动化测试-框架设计基础-Log4j 2实现日志输出-上篇(详解教程)

    1.简介 Apache Log4j 是一个非常古老的日志框架,并且是多年来最受欢迎的日志框架. 它引入了现代日志框架仍在使用的基本概念,如分层日志级别和记录器. 2015 年 8 月 5 日,该项目管 ...

  8. Linux 执行文件查找命令 which 详解

    某个文件不知道放在哪里了,通常可以使用下面的一些命令来查找: which  查看可执行文件的位置 whereis 查看文件的位置 locate   配合数据库查看文件位置 find   实际搜寻硬盘查 ...

  9. systemctl命令用法详解

    systemctl命令用法详解系统环境:Fedora 16binpath:/bin/systemctlpackage:systemd-units systemctl enable httpd.serv ...

随机推荐

  1. mantis统计报表和图形报表出现乱码问题的解决方法

    Mantis 报表中文乱码 1.安装Mantis图表 1.0插件 administrator登录-------管理------插件管理,安装插件 2.上传字体simhei.ttf  simsun.tt ...

  2. Python 基于固定 IP 来命名 ARM 虚拟机的实现

    问题描述 希望通过 Python 批量创建 ARM 虚拟机,并且在虚拟机命名时加入固定 IP 信息,方便管理维护. 问题分析 在创建 ARM 虚拟机之前,先创建固定 IP,然后获取固定 IP 地址,创 ...

  3. Mysql Order By 注入总结

    前言 最近在做一些漏洞盒子后台项目的总结,在盒子多期众测项目中,发现注入类的漏洞占比较大.其中Order By注入型的漏洞也占挺大一部分比例,这类漏洞也是白帽子乐意提交的类型(奖金高.被过滤概率小). ...

  4. 6 - 常用模块(os,sys,time&datetime,random,json&picle,shelve,hashlib)

    导入模块 想使用 Python 源文件,只需在另一个源文件里执行 import 语句 import module1[, module2[,... moduleN] from语句让你从模块中导入一个指定 ...

  5. NO.009-2018.02.14《临江仙·送钱穆父》宋代:苏轼

    临江仙·送钱穆父_古诗文网 临江仙·送钱穆父 宋代:苏轼 一别都门三改火,天涯踏尽红尘.依然一笑作春温.无波真古井,有节是秋筠.自从我们在京城分别一晃又三年,远涉天涯你奔走辗转在人间.相逢一笑时依然像 ...

  6. libxml的使用 编辑节点

    libxml读取的基本功能已经介绍过了,现在将介绍libxml编写的基本功能. 编写操作包含节点的添加,删除和修改. 对于添加,我们需要调用xmlNewTextChild函数来添加节点,需要xmlNe ...

  7. IOS 多媒体 使用总结

    一.音频播放 1.音效播放(短时间的音频文件)1> AudioServicesCreateSystemSoundID2> AudioServicesPlaySystemSound 2.音乐 ...

  8. 关于layui问题

    编辑: $('#Teacher').find('option').each(function(){ $(this).attr('selected',$(this).val()==data.tid); ...

  9. Tarjan的缩点&&割点概述

    What is Tarjan? Tarjan,是一种用来解决图的联通性的一种有效途径,它的一般俗称叫做:缩点.我们首先来设想一下: 如果我们有一个图,其中A,B,C构成一个环,那么我们在某种条件下,如 ...

  10. Xcode DeviceSupport

    问题:Could not locate device support files. This iPhone 6s is running iOS 12.1 (16B5059d), which may n ...