最近在看《Linux程序设计》这本书,里面讲到makefile相关的知识,所以想写篇博客记录一下自己的学习情况。由于这本书里有关makefile的内容比较少,只是一些初级的知识,而且不够系统性,所以看到这篇博客的朋友如果想系统性地了解makefile的知识可以去查阅一些写得更好的博客,或者去查看make命令的手册。好了,下面开始介绍makefile。

经常在Windows下写程序的朋友可能用的比较多的是IDE,它不需要你去设置太多的东西就可以编译、链接自己写的代码,虽然在Linux下也有IDE可以使用,但有时候需要自己手动编译和链接文件,比如在bash里手动输入命令去编译.c文件,链接.o文件。在编写小程序时,许多人都会在编辑完源文件后重新编译所有文件来重建应用程序。但对大型程序来说,使用这种简单的处理方式会带来一些明显的问题:编辑——编译——测试 这一循环周期变长,改动一小部分代码导致所有源文件重新编译是不可接受的。

比如说,你有3个头文件:a.h、b.h、c.h,3个C源文件main.c、2.c、3.c,具体情况如下:

1
2
3
4
5
6
7
8
9
10
11
/* main.c */
#include "a.h"
...
/* 2.c */
#include "a.h"
#include "b.h"
...
/* 3.c */
#include "b.h"
#include "c.h"
...

如果你修改了a.h文件,make工具检测到main.c、2.c包含了这个文件,在重新编译的时候就只会对main.c、2.c进行重新编译,而不会重新编译3.c这个文件,当项目中源文件多的时候可以减少很多重新编译的时间。当你写好makefile文件的时候,每次编译文件只需要输入make命令即可,省去了一条一条输入命令的时间,而且在需要修改命令的时候只需要对makefile文件的相应部分做出修改。

一、make命令
make程序本身有许多选项,其中最常用的3个选项如下:

  • -k:它的作用是让make命令在发现错误时仍然继续执行,而不是在遇到一个错误后就停止,这样可以一次性检查出所有未编译成功的源文件。
  • -n:它的作用是让make命令输出将要执行的操作步骤,而不是真正执行这些操作。
  • -f :它的作用是告诉make命令将哪个文件作为makefile文件。如果未使用这个选项,make命令默认将查找当前目录下名为makefile的文件,如果该文件不存在,它就会查找名为Makefile的文件。有些Linux发行版系统中,使用的可能是GNU Make,这个版本的make命令将优先查找GNUmakefile,然后才是makefileMakefile。更多make命令请使用$ man make查阅。

二、makefile基本用法
makefile文件的基本写法如下:

1
2
3
target: prerequisites  
command
...

  1. target:即目标文件,是你希望生成的文件,它可以是执行文件,也可以是链接文件.o,还可以是库文件.a,甚至是使用手册等。
  2. prerequisites:依赖项,是你生成target所需要的文件,它可以是头文件.h,源文件.c,链接文件.o,等。
  3. command:命令,即通过依赖项生成目标文件的方法。在makefile文件中必须以制表符tab开头,而不能用空格键,这算是一个历史遗留问题。此外,如果makefile文件中的某行以空格结尾,也可能导致make命令执行失败。

下面是一个简单的makefile文件:

1
2
3
4
5
6
7
8
9
  
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

这个例子中有4个基本语句,拿第一个语句来解释,myapp是目标文件,main.o 2.o 3.o是依赖项,gcc -o myapp main.o 2.o 3.o是命令语句。

在保证所有.h.c文件都创建的情况下运行$ make命令:

1
2
3
4
5
6
$ make
gcc -c main.c
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
$

make命令会自动判断创建文件的正确顺序,即使把最后的目标myapp放在最前面,同时会在执行时将命令显示出来。现在,我们改变其中一个头文件b.h,然后再重新执行make命令:

1
2
3
4
5
6
$ touch b.h
$ make
gcc -c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o 3.o
$

可以发现,这个过程并没有重新编译main.c文件,因为main.c中并没有引用b.h文件,所以make命令会在重建myapp的时候使用最少的命令,这样就大大减少了重新编译项目的时间。makefile文件中注释以#开头,延续到这一行结束。

三、makefile文件中的宏
上述内容已经足够应付日常小型项目的管理了,但是对于包含非常多源文件的大型项目来说,它们就显得缺乏弹性。因此,makefile文件允许使用宏以一种更通用的格式来书写。
宏的语句是MACRONAME=value,引用宏的方法是$(MACRONAME)${MACRONAME},有些make版本还接受$MACRONAMEvalue的值可以为空。一个加入宏定义的makefile文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
all: myapp

# Which complier
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

这里将编译器名字和编译选项、引用文件目录等替换为对于宏定义,类似于源文件中的宏定义方式。之后如果想修改编译器命令只需要修改少数行即可。

Comments

2016-06-22