浅谈 GNU Make 构建项目实践
摘要
本文简明地介绍 make
的基础原理,并组合实际项目经验,由浅入深讨论了一种实用 的 makefile
通用规则与模板的编写方案。对其中涉及的语法功能技巧择要阐述,希望 有助于初学者理解。
make 原理简介
make
是 linux/unix 系统下的一款工具,就如同 ls/cp/find/grep
这类程序一样属 于基础、通用且经典的设施。其基本原理是根据目标文件与依赖文件的(时间戳)新旧关 系,如果依赖文件更新了(目标文件更旧了),就执行指定的一系列操作。因此它需要这 两部分关键信息,或按 make 的术语叫“规则”:
- 哪个目标文件,会依赖哪些目标文件
- 满足条件后要执行什么样的操作
用户需要将这些规则写入配置文件,以指示 make
的运行,习惯上取名为 makefile
或 Makefie
,也可取为其他名字,那就需要额外加上 -f
选项,如:
make
make -f filename_other_than_makefile
一个简单的 makefile
规则内容如下:
oldfile: newfile
echo Hello World
echo 'cp newfile oldfile' >> make.log
cp newfile oldfile
这里假设 makefile
文件所在目录中另有两个文件名为 oldfile
与 newfile
,只 要在该目录下执行 make
命令,make
程序就会比较这两个文件的时间戳,如果 newfile
更新了,就会将其复制到 oldfile
(作为备份之用?),如果没有更新就 不会有任何操作。
可以配置多条操作,凡能在 shell 命令行执行的操作都行。每操作配置需要缩进一个 TAB
键,目标与依赖文件不能缩进,用一个 :
分隔。这就是 makefile
文件的基 本格式要求,当然还有其他许多语法细节,诸如变量引用、变量替换函数、目标依赖链、 隐式规则等,那就不是本文所能细说的了,请参考其他入门教程以及官方手册。
简单 C 程序的构建
虽然可以花式操作用 make
做些奇怪的事,但 make
的主要用途是构建 C/C++ 程序 。一般地,从 C/C++ 源文件到最终可执行程序要经过预处理、编译、链接等多步流程, make
就是为简化这种构建流程而诞生的。
单文件程序
先看个最简单的情况,假设只有一个 main.c
文件,那么 makefile
可如下写法:
main.exe: main.o
gcc -o main.exe main.o
main.o: main.c
gcc -c -o main.o main.c
这规则文件表明,main.exe
文件依赖 main.o
,而 main.o
又依赖 main.c
。 于是若执行 make
命令,就相当于依次执行如下两条命令:
gcc -c -o main.o main.c
gcc -o main.exe main.o
结果会生成 main.exe
可执行文件,当然在 linux 系统下可执行文件不须也不推荐加 上 .exe
后缀名,此只为说明方便起见。
另外,gcc
编译器有很多默认行为,可直接执行 gcc -c main.c
生成默认 main.o
的目标文件,不必加 -o
选项。甚至直接 gcc main.c
一步到位,自动预处理、编译 、链接成可执行文件,不过若无 -o
指定输出文件名,默认生成的是 a.out
。
正因为构建 C/C++ 程序是 make
的拿手好戏,它专门为此默认了一些隐式规则,以简化 makefile
的编写,比如 *.o
目标文件的依赖与生成规则就可以省略。简化为一条规 则:
main.exe: main.o
gcc -o main.exe main.o
多文件程序
当然对于单文件程序的编绎,没必要写 makefie
,直接 gcc -o main.exe main.c
一条命令解决问题。但假如有多个文件,除 main.c
外,还有两个额外的辅助源文件 util1.c
util2.c
(及相应的 .h
头文件)。这时直接一条命令虽然也能完成编译 链接工作:
gcc -o main.exe *.c
但有个问题,只要改了一个源文件,就要重新编译所有源文件。所以 make
就有用了, 可写个 makefile
如下:
TARGET = main.exe
ALL_OBJ = main.o util1.o util2.o
CFLAGS += -Wall
$(TARGET) : $(ALL_OBJ)
gcc $(CFLAGS) -o $(TARGET) $(ALL_OBJ)
这里用到了变量,语法类似 shell ,不过引用变量要加括号 $(VAR_NAME)
。也省略了 从 .c
到 .o
的编译规则,但按 make
的隐式规则,也会自动编译生成三个 .o
中间文件。如果后来修改了一个 .c
文件,则 make
只会重新编译一个 .o
文件, 另外两个目标不用重新编译,然后将新编译 .o
文件与原来无改动的 .o
文件一起链 接生成新的 main.exe
最终可执行文件。
make
命令可以加参数的,参数就是所用 makefile
内定义的目标(文件)名,默认 是文件内定义的第一个目标。假如在开发中改过 util1.c
文件,只是暂时想 检查一下语法有没错误,可以明确提供目标参数:
make util1.o
这里的 util1.o
能由隐式规则生成,故而也是可用的。这比直接在命令行写 gcc -c util1.c
的优势是可在 makefile
定义一系列编译选项,避免记不住或每次 输入麻烦的问题。
头文件包含依赖处理
前述的三文件例程,还漏了重要一点,没加入头文件依赖。最终可执行 main.exe
只依 赖几个 .o
文件,而每个 .o
文件只依赖对应的 .c
文件,在上个