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命令和makefiles

   make命令有一些内置的默认功能,但是光有这个它自己还是不知道怎么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.o3.o,当2.o3.o改变了,需要重新编译myapp.

 当需要编译多个文件,可以使用目标文件名all.假设应用程序包含一个二进制文件myapp和一个用户指导页面myapp.1,可以使用下面的方式指定。

all:  myapp  myapp.1

规则:makefile的第二部分内容。告诉我们将如何创建目标文件。

make命令决定2.o需要重新编译,使用什么命令进行编译呢?也许最简单的方式是使用gcc –c 2.c(make命令也有许多默认的规则),但是如果需要包含头文件的目录以及为调试设置符号信息怎么办呢?可以在makefile当中使用显示的规则。

 

 重要:所有的规则都必须要以制表符(tab)开头的行,空格不行。由于多个空格和tab看起来的效果很相同,但在Unix编程当中,tabsspaces还是有区别的,会导致一些问题。在makefile文件末尾加入空格行会导致make命令不能执行,因为缺少tab

例子:一个简单的 makefile,命名为Makefile1

myapp: main.o 2.o 3.o

gcc −o myapp main.o 2.o 3.o  //该行必须要有一个tab开头

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


 

使用$ 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 compiler

CC = gcc

 

# Where to install

INSTDIR = /usr/local/bin

 

# Where are include files kept

INCLUDE = .

 

# Options for development

CFLAGS = -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或者是LinuxGCC下。在MS-DOS下的源文件后缀名大多为.cpp而不是C,而Linux下的make命令没有内置的规则来编译.cpp文件(.cc的编译规则)

我们需要让make产生一个新规则来生成 .cpp的目标文件,这时需要添加一个后缀规则。Makefile5:

all: myapp

 

.SUFFIXES:      .cpp

 

.cpp.o:

         $(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $<

 

# Which compiler

CC = gcc

 

# Where to install

INSTDIR = /usr/local/bin

 

# Where are include files kept

INCLUDE = .

 

# Options for development

CFLAGS = -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 compiler

CC = gcc

 

# Where to install

INSTDIR = /usr/local/bin

 

# Where are include files kept

INCLUDE = .

 

# Options for development

CFLAGS = -g -Wall -ansi

 

# Options for release

# CFLAGS = -O -Wall -ansi

 

# Local Libraries

MYLIB = mylib.a

 

myapp: main.o $(MYLIB)

         $(CC) -o myapp main.o $(MYLIB)

 

 

$(MYLIB): $(MYLIB)(2.o) $(MYLIB)(3.o)

main.o: main.c a.h

2.o: 2.c a.h b.h

3.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条命令

-MMgcc会输出目标文件所依赖的源文件。

 

$ 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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章