Makefile 快速入门教程
本教程旨在帮助读者全面深入地掌握 Makefile 的编写与使用。Makefile 是一种用于自动化编译和构建程序的文件,通过定义一系列规则和命令,能够高效地管理项目的编译过程,尤其在大型项目中,其优势尤为明显。本教程从基础概念入手,逐步深入到高级特性,涵盖了 Makefile 的各个方面,包括宏的定义与使用、特殊宏、目标规则、隐式规则、自定义后缀、指令、递归使用等,旨在使读者能够灵活运用 Makefile,提高编程效率和项目管理能力。
一、Makefile 基础
宏的定义和使用
宏是 Makefile 中的变量,使用 =
号赋值。
CC = gcc
CFLAGS = -std=c99 -fexec-charset=GBK -finput-charset=UTF-8
定义了 CC
为 gcc
,CFLAGS
为 -std=c99 -fexec-charset=GBK -finput-charset=UTF-8
。使用宏时,在宏名称前后加 $()
或 ${}
,例如:
all: main
main: main.o hello.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
main.o: main.c
$(CC) $(CFLAGS) -c $<
hello.o: hello.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o
这里 $(CC)
和 $(CFLAGS)
会被替换为相应的变量值。
特殊宏
$@
:目标文件的名称。$<
:第一个依赖文件的名称。$^
:所有依赖文件的名称。
示例:
main: main.o hello.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
此例中 $@
为 main
,$^
为 main.o hello.o
。
二、完整 Makefile 示例
CC = gcc
CFLAGS = -std=c99 -fexec-charset=GBK -finput-charset=UTF-8
LDFLAGS = -lm
all: main
main: main.o hello.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
main.o: main.c
$(CC) $(CFLAGS) -c $<
hello.o: hello.c
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o
该 Makefile 定义了编译器、编译标志和链接标志,并利用宏来编译和链接程序。在当前目录下使用 make
或 make all
编译项目,使用 make clean
清理项目。make
默认寻找当前目录下的 makefile
文件并执行 all
部分,执行 make clean
或 make main.o
等会按相应规则执行。
三、常规宏
有各种默认宏,可通过 make -p
查看默认值。预定义变量(隐式规则中使用的宏)分为两类:
- 作为程序名称的宏(例如 CC)。
- 包含程序参数的宏(例如 CFLAGS)。
序号 | 变量和描述 |
---|---|
1 | AR 档案维护计划;默认为“ar”。 |
2 | AS 编译汇编文件的程序;默认为“as”。 |
3 | CC 编译 C 程序的程序;默认为“cc”。 |
4 | CO 从 RCS 签出文件的程序;默认为“co”。 |
5 | CXX 编译 C++ 程序的程序;默认为“g++”。 |
6 | CPP 运行 C 预处理器的程序,并将结果输出到标准输出;默认是 $(CC) -E 。 |
7 | FC 编译或预处理 Fortran 和 Ratfor 程序的程序;默认为“f77”。 |
8 | GET 从 SCCS 中提取文件的程序;默认为“获取”。 |
9 | LEX 用于将 Lex 语法转换为源代码的程序;默认为“lex”。 |
10 | YACC 用于将 Yacc 语法转换为源代码的程序;默认为“yacc”。 |
11 | LINT 用于在源代码上运行 lint 的程序;默认为“lint”。 |
12 | M2C 用于编译 Modula-2 源代码的程序;默认为“m2c”。 |
13 | PC 用于编译 Pascal 程序的程序;默认为“电脑”。 |
14 | MAKEINFO 将 Texinfo 源文件转换为 Info 文件的程序;默认为“makeinfo”。 |
15 | TEX 从 TeX 源代码制作 TeX dvi 文件的程序;默认为“tex”。 |
16 | TEXI2DVI 从 Texinfo 源制作 TeX dvi 文件的程序;默认为“texi2dvi”。 |
17 | WEAVE 将 Web 翻译成 TeX 的程序;默认为“编织”。 |
18 | CWEAVE 将 C Web 翻译成 TeX 的程序;默认为“cweave”。 |
19 | TANGLE 将 Web 翻译成 Pascal 的程序;默认为“缠结”。 |
20 | CTANGLE 将 C Web 翻译成 C 的程序;默认为“矩形”。 |
21 | RM 删除文件的命令;默认为“rm -f”。 |
这是程序名称的变量表,还有一个包含程序附加参数的变量表:
序号 | 变量和描述 |
---|---|
1 | ARFLAGS 提供存档维护程序的标志;默认为“rv”。 |
2 | ASFLAGS 在 .s 或 .S 文件上显式调用时提供给汇编器的额外标志。 |
3 | CFLAGS 提供给 C 编译器的额外标志。 |
4 | CXXFLAGS 提供给 C 编译器的额外标志。 |
5 | COFLAGS 提供给 RCS co 程序的额外标志。 |
6 | CPPFLAGS 提供给 C 预处理器和使用它的程序(例如 C 和 Fortran 编译器)的额外标志。 |
7 | FFLAGS 提供给 Fortran 编译器的额外标志。 |
8 | GFLAGS 提供给 SCCS 获取程序的额外标志。 |
9 | LDFLAGS 当编译器应该调用链接器时提供额外的标志,‘ld’。 |
10 | LFLAGS 给 Lex 的额外标志。 |
11 | YFLAGS 给 Yacc 的额外标志。 |
12 | PFLAGS 提供给 Pascal 编译器的额外标志。 |
13 | RFLAGS 为 Ratfor 程序提供给 Fortran 编译器的额外标志。 |
14 | LINTFLAGS 给予 lint 的额外标志。 |
可以在命令行定义宏,例如:make CPP = /home/courses/cop4530/spring02
。
四、Makefile - 依赖
简述
最终的二进制文件依赖于各种源代码和源头文件。例如:
hello: main.o factorial.o hello.o
$(CC) main.o factorial.o hello.o -o hello
这里 hello
依赖于 main.o
、factorial.o
和 hello.o
,当这些文件有变化时,make
会采取行动。同时需要定义如何准备 .o
文件:
main.o: main.cpp functions.h
$(CC) -c main.cpp
factorial.o: factorial.cpp functions.h
$(CC) -c factorial.cpp
hello.o: hello.cpp functions.h
$(CC) -c hello.cpp
五、Makefile - 规则
简述
Makefile 目标规则的一般语法是:
target [target...] : [dependent....]
[ command...]
每个命令前面的制表符是必需的。示例:
hello: main.o factorial.o hello.o
$(CC) main.o factorial.o hello.o -o hello
当执行 make target
时,make
找到适用的目标规则,如果依赖项比目标新,make
会执行命令(宏替换后),如果依赖项需要建立,会先建立依赖项(递归)。make
会终止如果命令返回失败状态。以破折号开头的命令行上的状态会被 make
忽略,例如:
clean:
-rm *.o *~ core paper
make
在宏替换后会回显命令,可使用 @
关闭回显,例如:
install:
@echo You must be root to install
常见目标有 all
(或 make
)、install
和 clean
:
make all
:编译所有内容,方便本地测试。make install
:将应用程序安装到正确位置。make clean
:清理应用程序,删除可执行文件、临时文件、目标文件等。
Makefile 隐式规则
从源代码 x.cpp
构建可执行 x
的隐含规则:
.cpp:
$(CC) $(CFLAGS) $@.cpp $(LDFLAGS) -o $@
此规则说明如何从 x.cpp
生成 x
,是隐含规则,可在所有情况下使用。从 .cpp
构建 .o
文件的常见隐含规则:
.cpp.o:
$(CC) $(CFLAGS) -c
或
.cpp.o:
$(CC) $(CFLAGS) -c $*.cpp
六、Makefile - 自定义后缀
简述
make
可自动创建 .o
文件,利用此优势可缩短 Makefile。例如:
OBJECTS = main.o hello.o factorial.o
hello: $(OBJECTS)
cc $(OBJECTS) -o hello
hello.o: functions.h
main.o: functions.h
factorial.o: functions.h
make
使用 .SUFFIXES
特殊目标来定义自己的后缀,例如:
.SUFFIXES:.foo.bar
可定义自己的规则,例如:
.foo.bar:
tr '[A-Z][a-z]' '[N-Z][A-M][n-z][a-m]' <
< $@
.c.o:
$(CC) $(CFLAGS) -c
第一条规则允许从 .foo
文件创建 .bar
文件,第二条是默认的从 .c
文件创建 .o
文件的规则。
七、Makefile - 指令
简述
make
程序上的指令有多种形式,不同 make
程序支持的指令可能不同,这里主要介绍 GNU make 支持的指令。
条件指令
- ifeq:开始条件,包含两个参数,用逗号分隔并用括号括起来,对参数进行变量替换并比较,若匹配则遵循后续行,否则忽略。
- ifneq:开始条件,与
ifeq
相反。 - ifdef:开始条件,包含单个参数,若参数为真则条件为真。
- ifndef:开始条件,包含单个参数,若参数为假则条件为真。
- else:若前一个条件失败,遵循后续行,在条件中可选。
- endif:结束条件,每个条件必须以
endif
结尾。
语法:
conditional-directive
text-if-true
endif
或
conditional-directive
text-if-true
else
text-if-false
endif
示例:
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
包含指令
include directive 允许 make
暂停读取当前 makefile 并读取其他 makefile,是 makefile 中的一行:
include filenames...
文件名可包含 shell 文件名模式,行首可含空格但不能有制表符。例如:
include foo *.mk $(bar)
等同于:
include foo a.mk b.mk c.mk bish bash
make
处理该指令时暂停读取当前 makefile,依次读取列出的文件,读完后继续。
覆盖指令
若使用命令参数设置了变量,会忽略 makefile 中的普通赋值,若想在 makefile 中设置变量,即使它被命令参数设置过,可使用覆盖指令:
override variable = value
or
override variable := value
八、Makefile - 重新编译
简述
make
是智能程序,根据源文件更改工作。例如有 main.cpp
、hello.cpp
、factorial.cpp
和 functions.h
,functions.h
被其他文件依赖,main.cpp
依赖 hello.cpp
和 factorial.cpp
,对 functions.h
更改会重新编译所有源文件,对 main.cpp
更改只会重新编译 main.cpp
。make
编译文件时检查目标文件时间戳,若源文件时间戳比目标文件新,会生成新目标文件。
避免重新编译
对于大型项目,可能不想因头文件更改重新编译依赖文件。可使用 -t
标志,告诉 make
不运行规则中的命令,而是通过更改最后修改日期将目标标记为最新:
- 使用
make
重新编译需重新编译的源文件。 - 在头文件中更改。
- 使用
make -t
将目标文件标记为最新。
若头文件已改且部分文件需重新编译,可使用 -o file
标志将指定文件标记为“旧”:
- 使用
make -o header file
重新编译需要编译的源文件(多个头文件时对每个使用-o
选项)。 - 使用
make -t
更新目标文件。
九、Makefile - 其他功能
Make 的递归使用
递归使用 make
是在 makefile 中使用 make
作为命令,适用于为子系统生成文件。例如:
subsystem:
cd subdir && $(MAKE)
or, equivalently:
subsystem:
$(MAKE) -C subdir
了解其工作原理和子 make 与顶级 make 的关系。
将变量传递给子制作
顶层 make
的变量值可通过环境传递给子 make,在子 make 中作为默认值。使用 -e
开关可覆盖子 makefile 中指定的内容。make
将变量及其值添加到运行命令的环境中,子 make 用环境初始化变量值表。特殊变量 SHELL 和 MAKEFLAGS 始终被导出,除非取消导出,MAKEFILES 被设置时会被导出。导出变量使用:
export variable...
防止变量导出使用:
unexport variable...
变量 MAKEFILES
若定义了环境变量 MAKEFILES,make
将其值视为要在其他文件前读取的附加 makefile 名称列表,类似 include 指令,在不同目录搜索。主要用于递归调用 make
之间的通信。
包括来自不同目录的头文件
若头文件在不同目录且在不同目录运行 make
,使用 -I
选项提供头文件路径。例如:
INCLUDES = -I "/home/jc2182/header"
CC = gcc
LIBS = -lm
CFLAGS = -g -Wall
OBJ = main.o factorial.o hello.o
hello: ${OBJ}
${CC} ${CFLAGS} ${INCLUDES} -o $@ ${OBJS} ${LIBS}
.cpp.o:
${CC} ${CFLAGS} ${INCLUDES} -c
将更多文本附加到变量
使用 +=
可向已定义变量添加更多文本,例如:
objects += another.o
等同于:
objects = main.o hello.o factorial.o
objects := $(objects) another.o
Makefile 中的续行
可使用反斜杠 \
换行,例如:
OBJ = main.o factorial.o \
hello.o
等同于:
OBJ = main.o factorial.o hello.o
从命令提示符运行 Makefile
若有 Makefile
,直接使用 make
运行,若 Makefile 是其他名称,使用:
make -f your-makefile-name
十、Makefile - 示例
简述
一个编译 hello
程序的 Makefile 示例,程序由 main.cpp
、factorial.cpp
和 hello.cpp
组成。
# Define required macros here
SHELL = /bin/sh
OBJS = main.o factorial.o hello.o
CFLAG = -Wall -g
CC = gcc
INCLUDE =
LIBS = -lm
hello:${OBJ}
${CC} ${CFLAGS} ${INCLUDES} -o $@ ${OBJS} ${LIBS}
clean:
-rm -f *.o core *.core
.cpp.o:
${CC} ${CFLAGS} ${INCLUDES} -c
可使用 make
构建 hello
程序,使用 make clean
删除目标文件和核心文件。