对于linux c/c++开发人员来说 ,不了解makefile是无法原谅的一件事情。在大型的项目中,makefile的强大之处被完全体现出来。
首先makefile用来解决多个源文件的编译问题的,但他的功能不仅限于此,make命令和makefile文件的结合提供了一个在项目管理领域十分强大的工具。它不仅常被用于控制源代码的编译,而且还用于手册页的编写以及将应用程序安装到目标目录。
1.makefile的基本语法
makefile文件由一组依赖关系和规则构成。每个依赖关系由一个目标(即将要创建的文件)和一组目标所依赖的源文件所组成。规则就是用来描述如何通过依赖文件创建目标的。
make命令会读取makefile文件的内容,确定目标文件,然后比较该目标所依赖的源文件的日期和时间决定采用哪条规则来构造目标。通常在创建最终目标文件之前,它需要创建一些中间目标。
先来看看几个make选项:
-k:它的作用是让make命令在发现错误时仍然继续执行。
-n:它的作用是让make命令输出将要执行的操作步骤,而不真正执行这些操作。
-f <filename> :它的作用是告诉make命令将哪个文件作为makefile文件,若不使用该选项标准的make命令将首先在当前目录下查找名为makefile的文件,若该文件不存在,他会继续查找名为Makefile的文件
make命令如果不加上参数,它将试图创建列在makefile文件中的第一个目标。通常我们将第一个目标定义为all,然后在列出其他从属目标,定义all目标也可以帮助我们创建多个目标。
首先来看一个makefile文件:makefile1
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
以上的makefile文件阐述了makefile的2个基本要素:依赖关系和规则。
myapp依赖于main.o 2.o 3.o ,而main.o依赖于main.c和a.h等等,它们形成了一个层次结构,显示了源文件之间的关系,如果文件b.h发生变化,你就需要重现编译2.o和3.o,从而还需要重新创建myapp。
gcc -o myapp main.o 2.o 3.o gcc -c main.c 等 就是规则。这里需要注意的一点是,规则所在的行必须以制表符开头,用空格的不行的。
下面列出程序清单:
/* main.c */
#include<stdlib.h>
#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(){
}
创建3个空的头文件:
touch a.h
touch b.h
touch c.h
执行make命令 :
[aaa@localhost fileformake]$ make -f makefile1
gcc -c main.c
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
我们可以修改文件b.h来测试依赖关系:
[aaa@localhost fileformake]$ touch b.h
[aaa@localhost fileformake]$ make -f makefile1
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
和预想的一样,依赖b.h的目标文件都将重新编译。
2.makefile的注释和宏定义
makefile的注释以#开头直到行尾。
makefile的宏定义常被用于编译器的选项,在实际开发中非常有用。把makefile1写成带有宏定义的形式makefile2:
all: myapp
#which compiler
CC = gcc
#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
执行make命令:
[aaa@localhost fileformake]$ rm *.o myapp
[aaa@localhost fileformake]$ make -f makefile2
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o 3.o
宏定义使得我们对软件的编译变得十分灵活,make命令内置了一些特殊的宏定义 也十分有用:
$? 表示当前目标所依赖的文件列表中比当前文件还要新的文件
$@ 表示当前目标的名字
$< 表示当前依赖文件的名字
$* 表示不包括后缀名的当前依赖文件的名字
此外还有2个特殊的字符,他们出现在命令之前:
- : 告诉make命令忽略所有错误(例如:使用mkdir创建目录时,目录已存在,使用此符号可避免报错);
@ :告诉make命令在执行某条命令之前不要讲该命令显示在标准输出上。(用法:;例如@rm -rf 123.txt )
3.多个目标的makefile
在makefile2的基础上创建makefile3:
all: myapp
#which compiler
CC = gcc
#where to install
INSTDIR = /home/luo/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
新添加2个目标clean和install,有几个值得说明的地方:
a. rm以减号开头,make会忽略rm命令的执行结果;
b. clean没有依赖关系,该目标总被认为是过时的,所以clean对应的规则将总被执行;
c. install目标依赖myapp,故要执行install必须先创建myapp。
执行make:
[aaa@localhost fileformake]$ rm *.o myapp
[aaa@localhost fileformake]$ make -f makefile3 install
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o 3.o
installed in /home/luo/bin
[aaa@localhost fileformake]$ make -f makefile3 clean
rm main.o 2.o 3.o