Linux中的make和makefile
1.1 多源文件问题
我们平常编写一些小的程序一般都是修改源代码后,重新编译所有的源文件,但是对于大型的项目,这种方法就会有许多的问题。edit?compile?test周期耗费的时间比较长,所以当只修改一个文件时,要避免编译所有的源文件。
假设有头文件a.h, b.h , c.h和C的源文件main.c, 2.c , 3.c。它们的关系如下:
/* main.c */#include "a.h".../* 2.c */#include "a.h"#include "b.h".../* 3.c */#include "b.h"#include "c.h"
如果c.h修改了,源文件main.c和2.c应该不需要重新编译,因为它们不依赖c.h头文件。而源文件3.c依赖头文件c.h需要重新编译。如果b.h修改了,我们忘记重新编译2.c也会导致程序功能异常。 而make工具可以解决上面这两个问题。
1.2 make命令和makefilesmake命令有一些内置的默认功能,但是光有这个它自己还是不知道怎么build程序。我们必须提供一个文件告诉make应用程序的构造,这个文件就是makefile.
Make和makefile提供了强大的功能来管理项目的编译以及发布install到指定文件夹。
1.2.1 makefiles语法一个makefile文件包括一系列的依赖(dependency))关系和规则(rules)。一个依赖有一个目标文件和一系列目标文件依赖的源文件。规则描述了怎么用依赖文件创建目标文件。通常,一个目标文件是单个可执行文件。Make命令通过读取makefile来决定哪些源文件和目标文件需要重新编译,根据比较它们的日期和时间来决定使用来个规则来构建targets。
1.2.2 make的选项与参数
下面为make 命令经常用到的三个选项:
-k:告诉make当发生错误时继续执行,而不是停在第一个错误的位置。
-n:告诉make打印出它将要完成什么,实际上没做。
?f <filename>,告诉make用哪个makefile,如果不使用这个选项,make将搜索当前目录下第一个名为makefile的文件,如果还不存在,搜索一个文件名为Makefile。
依赖:指明了每个文件怎么样关联源文件。
在makefile文件中,写依赖的方法为:目标文件名,冒号,空格或tabs,然后接着是用空格或 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.o, 2.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: myapp myapp.1
规则:是makefile的第二部分内容。告诉我们将如何创建目标文件。
当make命令决定2.o需要重新编译,使用什么命令进行编译呢?也许最简单的方式是使用gcc –c 2.c(make命令也有许多默认的规则),但是如果需要包含头文件的目录以及为调试设置符号信息怎么办呢?可以在makefile当中使用显示的规则。
重要:所有的规则都必须要以制表符(tab)开头的行,空格不行。由于多个空格和tab看起来的效果很相同,但在Unix编程当中,tabs和spaces还是有区别的,会导致一些问题。在makefile文件末尾加入空格行会导致make命令不能执行,因为缺少tab。
例子:一个简单的 makefile,命名为Makefile1
myapp: main.o 2.o 3.ogcc ?o myapp main.o 2.o 3.o //该行必须要有一个tab开头main.o: main.c a.hgcc ?c main.c2.o: 2.c a.h b.hgcc ?c 2.c3.o: 3.c b.h c.hgcc ?c 3.c
使用$ make ?f Makefile1
这是由于make命令分析到目标文件为myapp,然后查看依赖文件main.o,需要main.c来创建,但是开始没有创建该文件。
使用下面的命令创建三个空头文件:
$touch a.h
$touch b.h
$touch c.h
/* main.c */#include "a.h"extern void function_two();extern void function_three();int main(){function_two();function_three();exit (EXIT_SUCCESS);}/* 2.c */#include "a.h"#include "b.h"void function_two() {}/* 3.c */#include "b.h"#include "c.h"void function_three() {}
$ make ?f Makefile1
Make按顺序显示出了它执行的命令
现在修改b.h头文件:
$ touch b.h
$ make ?f Makefile1
Make只显示了创建依赖于b.h的目标 文件的命令
删除2.o
$ rm 2.o
$ make ?f Makefile1
由上面可以看到make 可以正确的决定执行gcc命令的顺序。
在makefile中使用#进行注释。
1.2.3 makefiles宏对于文件比较多的项目,可以在makefile中定义宏。
定义:MACRONAME=value
获取值:$(MACRONAME) or ${MACRONAME}.
宏经常在makefile中用来为编译器指定编译选项。对于发布版本和调试版本程序需要的信息不同,对于release 版本不需要加入调试信息。对于Makefile1,默认使用的是gcc编译器,在其他的Unix操作系统,可能使用cc或cc89,如果想让makefile在不同的操作系统上运行,需要修改makefile文件中的多行。可以使用宏来解决这问题。
Makefile2:
all: myapp # 指定编译器CC = gcc # 头文件目录INCLUDE = . # 开发编译选项CFLAGS = -g -Wall -ansi # 发布版本编译选项# 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 b.h c.h $(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
执行下面命令:
$ rm *.o myapp
$ make ?f Makefile2
实际上,make有许多特殊的内部宏,可以让makefile更简洁。下面列出一些经常用到的内部宏。
$?
List of prerequisites changed more recently than the current target.
$@
Name of the current target.
$<
Name of the current prerequisite.
$*
Name of the current prerequisite, without any suffix.
Makefile中另外两个非常重要的特殊字符:
“-“让make忽略任何错误。
“@”让make在命令执行前不要向标准输出打印命令。
1.2.4 多目标文件可以使用”clean”选项来清除不想要的目标文件,用”install”选项来将应用程序移动到不同的目录中。Makefile3
all: myapp # Which compilerCC = gcc # Where to installINSTDIR = /usr/local/bin # Where are include files keptINCLUDE = . # Options for developmentCFLAGS = -g -Wall -ansi # Options for release# 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 b.h c.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
install依赖于myapp.,make需要先生成myapp。
If前面的“@”是让make不要输出这些命令。
$ make ?f Makefile3 install
gcc ?o myapp main.o 2.o 3.o
Installed in /usr/local/bin
$ make ?f Makefile3 clean
rm main.o 2.o 3.o
1.2.5 内置规则Makefile能准确判断每个命令的执行步骤,make中包括许多内置的规则,当有许多源文件时,可以简化makefiles。创建一个foo.c文件:
#include <stdlib.h>#include <stdio.h>int main(){printf("Hello World\n");exit (EXIT_SUCCESS);}
使用make进行编译,不指定makefile.
$ make foo
Make命令知道怎么调用编译器,尽管使用的是默认的编译器cc.
可以使用 make –p来打印内置的规则。
我们可以用自定义的值来改变内置宏,从而改变它的默认行为。
$ rm foo
$ make CC = gcc CFLAGS = "?Wall ?g" foo
1.2.6 后缀规则
有时源文件需要在不同的编译器上进行编译:如在MS-DOS或者是Linux的GCC下。在MS-DOS下的源文件后缀名大多为.cpp而不是C,而Linux下的make命令没有内置的规则来编译.cpp文件(有.cc的编译规则)。
我们需要让make产生一个新规则来生成 .cpp的目标文件,这时需要添加一个后缀规则。Makefile5:
all: myapp .SUFFIXES: .cpp .cpp.o: $(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $< # Which compilerCC = gcc # Where to installINSTDIR = /usr/local/bin # Where are include files keptINCLUDE = . # Options for developmentCFLAGS = -g -Wall -ansi # Options for release# 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 b.h c.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
执行:
$ cp foo.c bar.cpp
$ make ?f Makefile5 bar
gcc ?xc++ ?g ?Wall ?ansi ?I. ?c bar.cpp
?xc++:告诉gcc是一个C++源文件
1.2.7 make管理库文件lib当开发一个大的项目,利用一个库(library)来管理多个编译版本产品非常方便。库文件的后缀名为(.a),它包含许多的目标文件。
Make命令中内置的规则来管理库:
.c.a:
$(CC) ?c $(CFLAGS) $<
$(AR) $(ARFLAGS) $@ $*.o
宏 $(AR) 和 $(ARFLAGS) 指的是命令 ar 和命令选项 rv。含义为:从一个 .c 文件到一个.a的库文件需要应用两条规则。第一条规则:必须编译源文件为目标文件;第二条规则:使用ar命令来添加该新的目标文件来生成库文件。如果目前有一个库文件fud包含目标文件bas.o,在第一条规则中,$<为bas.c。在第二条规则中,$@为fud.a,$*为bas.
下面让目标文件2.o 和3.o包含在库文件 mylib.a,如下Makefile6:
all: myapp # Which compilerCC = gcc # Where to installINSTDIR = /usr/local/bin # Where are include files keptINCLUDE = . # Options for developmentCFLAGS = -g -Wall -ansi # Options for release# CFLAGS = -O -Wall -ansi # Local LibrariesMYLIB = mylib.a myapp: main.o $(MYLIB) $(CC) -o myapp main.o $(MYLIB) $(MYLIB): $(MYLIB)(2.o) $(MYLIB)(3.o)main.o: main.c a.h2.o: 2.c a.h b.h3.o: 3.c b.h c.h clean: -rm main.o 2.o 3.o $(MYLIB) 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
$ rm ?f myapp *.o mylib.a
$ make ?f Makefile6
gcc ?g ?Wall ?ansi ?c main.c ?o main.o
gcc ?g ?Wall ?ansi ?c 2.c ?o 2.o
ar rv mylib.a 2.o
ar: creating mylib.a
c ? 2.o
gcc ?g ?Wall ?ansi ?c 3.c ?o 3.o
ar rv mylib.a 3.o
c ? 3.o
gcc ?o myapp main.o mylib.a
1.2.8 GCC的两个选项-JN –MM
-jN可以同时执行N条命令
-MM:gcc会输出目标文件所依赖的源文件。
$ gcc ?MM main.c 2.c 3.c
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
Make命令知道怎么调用编译器,尽管使用的是默认的编译器cc.
我们可以用自定义的值来改变内置宏,从而改变它的默认行为。
$ rm foo
$ make CC = gcc CFLAGS = "?Wall ?g" foo