虽然make命令内置了很多智能机制,但是无法了解应该如何建立应用程序的。你必须为其提供一个文件,告诉它应用程序应该如何构造,这个文件称为makefile。
makefile的语法
makefile文件由一组依赖关系和规则构成。每个依赖关系由一个目标(即将要创建的文件)和一组该目标所依赖的源文件组成。而规则描述了如何通过这些依赖文件创建目标。一般来说,目标是一个单独的可执行文件。
make命令会读取makefile文件的内容,它先确定目标文件或要创建的文件,然后比较该目标所依赖的源文件的日期和时间以决定该采用哪条规则来构造目标。通常在创建最终的目标文件之前,它需要先创建一些中间目标。make命令会根据makefile文件来确定目标文件的创建顺序以及正确的规则调用顺序。
make命令的选项和参数
make程序本身有许多选项,其中最常用的3个选项如下所示:
-k:它的作用是让make命令在发现错误时仍然继续执行,而不是在检测到第一个错误时就停下来。你可以利用这个选项在一次操作中发现所有未编译成功的源文件。
-n:它的作用是让make命令输出将要执行的操作步骤,而不真正执行这些操作。
-f<filename>:它的作用是告诉make命令将哪个文件作为makefile文件。如果未使用这个选项,make命令将首先在当前目录下查找名为makefile的文件,如果该文件不存在,它就会查找名为Makefile的文件。但如果你是在Linux系统中,你使用的可能是GNUMake,这个版本的make命令将在搜索makefile文件和Makefile文件之前,首先查找名为GNUmakefile的文件。习惯上,许多Linux程序员使用文件名Makefile,因为如果一个目录下都是以小写字母为名称的文件,则Makefile文件将在目录的文件列表中第一个出现。
为了指示make命令创建一个特定的目标(通常是一个可执行文件),你可以把该目标的名字作为make命令的一个参数。如果不这么做,make命令将试图创建列在makefile文件中的第一个目标。许多程序员都会在自己的makefile文件中将第一个目标定义为all,然后再列出其它从属目标。这个约定可以明确地告诉make命令,在未指定特定的目标时,默认情况下应该创建哪个目标。建议都坚持使用这一约定。
1依赖关系
依赖关系定义了最终应用程序里的每个文件与源文件之间的关系。
在makefile文件中,这些规则的写法是:先写目标的名称,然后紧跟着一个冒号,接着是空格或制表符tab,最后是用空格或制表符tab隔开的文件列表(这些文件用于创建目标文件)。
例:
myapp: main.o 2.o 3.o main.o: main.c a.h 2.o: 2.c a.h b.h 3.o: 3.c b.h c.h
它表示目标myapp依赖于main.o2.o和3.o,而main.o依赖于main.c和a.h等等
这组依赖关系形成一个层次结构,它显示了源文件之间的关系。如果文件b.h发生改变,你就需重新编译2.o和3.o,而由于2.o和3.o发生了改变,你还需要重新创建目标myapp。
如果想一次创建多个文件,你可以利用伪目标all。假设应用程序由二进制文件myapp和使用手册myapp.1组成。你可以用下面这行语句进行定义:
all:myappmyapp.1
这里再次强调,如果未指定一个all目标,则make命令将只创建它在文件makefile中找到的第一个目标。
2规则
makefile文件的第二部分内容是规则,它们定义了目标的创建方式。当make命令确定需要重建2.o时,它具体应该使用哪条命令呢?规则所在行必须以制表符tab开头,用空格是不行的。如果缺少了制表符tab,make命令就不会正常工作,所以发现这个错误很容易。
实验:一个简单的makefile文件
myapp: main.o 2.o 3.o gcc -o myapp main.o 2.o 3.o main.o: main.c a.h gcc -c main.c 2.o: 2.c a.h b.h gcc -c 2.c 3.o: 3.c b.h c.h gcc-c 3.c /*main.c*/ #include<stdlib.h> #include"a.h" externvoidfunction_two(); externvoidfunction_three(); int main() { function_two(); function_three(); exit(1); } /*2.c*/ #include"a.h" #include"b.h" void function_two() {} /*3.c*/ #include"b.h" #include"c.h" voidfunction_three() {}
makefile文件中的注释
makefile文件中的注释以#号开头,一直延续到这一行的结束。makefile文件中的注释可以帮助程序的编写者及其他人理解最初编写这个文件的目的。
makefile文件中的宏
通过语句MACRONAME=value在makefile文件中定义宏,引用宏的方法是使用$(MACRONAME)或${MACRONAME}。如果想把一个宏的值设置为空,你可以令等号后面留空。
makefile文件的另一个问题是,它假设编译器的名字是gcc,而在其他UNIX系统中,编译器的名字可能是cc。如果想将makefile文件移植到另一版本的UNIX系统中,或在现有系统中使用另一个编译器,为了使其工作,你将不得不修改makefile文件中许多行的内容。通过宏定义,你可以方便地修改这些内容。
实验:带宏定义的makefile文件
all:myapp #whichcompiler CC=gcc #whereareincludefileskept INCLUDE=. #optionsfordevelopment CFLAGS=-g -Wall -ansi #optionsforrelease #CFLAGS=-o -Wall -ansi myapp: main.o 2.o 3.o $(CC) -o myapp main.o 2.o 3.o main.o: main.c a.h $(CC) -I$(INCLUDE) $(CFLAGS) -c main.c 2.o: 2.c a.h b.h $(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c 3.o: 3.c c.h b.h $(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c 在makefile文件中,有两个有用的特殊字符,它们出现在命令之前。 -:告诉make命令忽略所有的错误。例如:你想创建一个目录,但又想忽略任何错误(比如目录已存在),你就可以在mkdir命令的前面加上一个减号。 @:告诉make在执行某条命令前不要将该命令显示在标准输出上。 实验:多个目标 all: myapp #whichcompiler CC=gcc #whereareincludefileskept INCLUDE=. #optionsfordevelopment CFLAGS=-g -Wall -ansi #optionsforrelease #CFLAGS=-o -Wall -ansi myapp: main.o 2.o 3.o $(CC) -o myapp main.o 2.o 3.o main.o: main.c a.h $(CC) -I$(INCLUDE) $(CFLAGS) -c main.c 2.o: 2.c a.h b.h $(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c 3.o: 3.c c.h b.h $(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c clean: -rm main.o 2.o 3.o install: myapp @if [ -d ${INSTDIR} ]; \ then \ cp myapp ${INSTDIR} && \ chmod a+x ${INSTDIR}/myapp && \ chmod og-w ${INSTDIR}/myapp && \ echo "Installed in ${INSTDIR}"; \ else \ echo "Sorry,${INSTDIR}does not exist";\ fi
由于make命令在执行规则时会调用一个shell,并且会针对每个规则使用一个新shell,所以必须在上面每行代码的结尾加上一个反斜杠\,让所有shell脚本命令在逻辑上处于同一行,并作为一个整体传递给一个shell执行。