概念
1.Makefile是一个工程管理工具,本质上就是一个文件,文件中存放的是代码编译规则
2.Makefile会根据文件的时间戳(文件修改的时间)来决定工程内的文件是否需要重新编译。大大提高编译效率
3.Makefile 是一种构建自动化工具的文件,用于管理和自动化软件构建过程。
格式
- 基本结构:目标(Target)、依赖(Dependency) 和 命令(Commands) 组成
target: dependencies command # target:目标文件,通常是一个可执行文件、目标文件(.o 文件)或伪目标(如 clean)。 # dependencies:依赖项,即目标文件生成所需的源文件或其他文件。 # command:生成目标文件所需执行的命令。注意:命令行必须以 Tab 键 开头。
- 在指定的目录下创建一个名字为:Makefile 或者 makefile的文件
- make clean
make clean #则会默认执行Makefile文件中clean指令下面的命令
make -f Makefile.debug clean #执行 Makefile.release 中的 clean
- @:取消回显 --》指令执行过程不在终端显示
- -:忽略命令的错误,继续执行后续命令
-rm -f temp.o
语法规则
- 命令部分必须以 Tab 键 开头,空格无效
变量
- 在 Makefile 中可以定义变量,以简化和复用常用的字符串。例如编译器、编译选项和文件列表。变量名通常是大写的,用 = 号进行赋值,使用时通过 $(VAR) 的方式引用
CC = gcc #指定使用 gcc 作为编译 C 程序的编译器
CFLAGS = -Wall -g
#-Wall:启用编译器的所有常见警告。Wall 是 "Warnings all" 的缩写,它会让编译器发出尽可能多的有用警告,帮助开发者发现潜在的代码问题。
#-g:生成调试信息。使用 -g 时,编译器会生成额外的调试符号,这些符号会被添加到可执行文件中,以便你可以使用调试器(如 gdb)调试程序。
- all
- 在 Makefile 中,all 通常被用作默认的目标(target),它是一个惯例,而不是必须使用的名字。
- all 目标的作用是汇总多个子目标,确保运行 make 时可以执行所有必要的构建步骤。
- 默认情况下,当你运行 make 而不指定具体目标时,Makefile 会执行第一个出现的目标,通常这个目标被命名为 all。
- 作用:
- 汇总多个目标:all 目标通常用来汇总所有你想要构建的文件或程序
- 默认目标:如果没有明确指定目标,make 默认执行第一个目标,因此将 all 作为第一个目标是个惯例,目的是让 make 在不指定其他目标时执行主要的构建操作
- clean
- clean 目标用于删除编译过程中生成的临时文件、目标文件(如 .o 文件)、可执行文件等。这有助于保持项目的目录干净,并在需要重新编译时避免旧文件的干扰。
- install
- install 目标用于将编译好的程序或文件复制到系统的安装目录(如 /usr/local/bin、/usr/local/lib 等)。在开源项目中,make install 通常用来安装可执行文件、库文件、头文件等到系统中。
install: install -m 755 my_program /usr/local/bin/ #运行 make install 会将可执行文件 my_program 安装到 /usr/local/bin。
- uninstall
- uninstall 目标用于删除通过 install 目标安装的文件。它是 install 的反操作,用于清理安装到系统目录中的程序。
uninstall: rm -f /usr/local/bin/my_program
- distclean
- distclean 目标是 clean 目标的扩展,除了删除编译产生的文件外,还会删除配置文件和其他生成的文件(如 Makefile 自己是由脚本生成的,distclean 可能会删除它)。这个目标常用于彻底清理项目,使其恢复到刚下载时的状态。
- 在clean的基础再删除
distclean: clean rm -f config.mk Makefile
- phony
- .PHONY 是一个特殊的伪目标,它告诉 Makefile 这个目标不是一个文件,而是一个操作。它通常用于避免 Makefile 将同名文件当成目标。
- .PHONY 通常用于声明一些不会生成文件的目标,例如 clean、all、install 等。
- .PHONY 声明,防止与实际文件同名时发生冲突。
- 等等
内置和自动变量
Makefile 中有许多内置的自动变量,用于简化规则的编写:
- $@:目标文件的名称。
- $^:所有依赖文件的列表。
- $<:第一个依赖文件的名称。
- $?:所有比目标更新的依赖文件的列表。
- 通俗地说,$? 是指那些自上次构建目标后被修改过的依赖文件。如果这些依赖文件比目标文件更新,那么 make 会重新构建目标文件,并且 $? 就包含了这些比目标更新的文件。
program: main.o utils.o
gcc -o program $?
#如果 main.o 最近被修改,而 utils.o 没有修改,那么 $? 在这次运行时就只包含 main.o,因为 main.o 是比 program 更新的文件。
gcc [选项] [源文件] -o [输出文件]#单源文件编译
gcc [编译选项] -o [输出文件] [依赖文件]#多文件项目的编译与链接
#输出文件在前面:-o 选项后紧跟输出文件名,用来告诉编译器输出结果应该保存在哪个文件中。
多目标和通配符
多目标
- 一个规则可以指定多个目标(通常是多个可执行文件或多个中间目标)。这种做法非常常见,尤其是在大型项目中,你可能有多个最终的目标需要生成。
all: program1 program2
#这个规则定义了 all 依赖于两个目标:program1 和 program2。
#当你运行 make all 或直接运行 make 时,Makefile 会先构建 program1 和 program2
构建 program1 和 program2
program1: main1.o
gcc -o program1 main1.o
program2: main2.o
gcc -o program2 main2.o
通配符
- Makefile 支持使用通配符来匹配文件,简化规则编写。通配符可以帮助处理多个相似的文件,比如所有的 .o 文件可以由对应的 .c 文件生成。
- 常见的通配符有 *、? 、% 等
通配符 | 说明 | 示例 | 匹配结果 |
---|---|---|---|
* | 匹配零个或多个任意字符 | *.c | 匹配所有 .c 文件 |
? | 匹配一个任意字符 | file?.c | 匹配 file1.c 、fileA.c ,不匹配 file12.c |
[...] | 匹配括号内的任意一个字符 | main[0-9].c | 匹配 main1.c 、main2.c 等 |
% | 匹配零个或多个任意字符(用于规则定义) | %.o: %.c | 将 .c 文件编译为 .o 文件 |
** | 递归匹配所有子目录中的文件(GNU Make 4.0+ 支持) | **/*.c | 匹配当前目录及所有子目录中的 .c 文件 |
$(wildcard pattern) | 查找与模式匹配的文件列表 | $(wildcard *.c) | 查找当前目录中的所有 .c 文件 |
- 通配符匹配 .o 文件
OBJS = $(wildcard *.o)
OBJS 变量现在包含了当前目录下所有以 .o 结尾的文件名。
通配规则
通配规则用于简化目标的生成规则,特别是当你有很多相似的规则时,使用通配符可以自动生成目标而无需手动定义每个目标的构建规则。
%.o: %.c
gcc -c $< -o $@
#%.o: %.c:这个规则的意思是,所有 .o 文件由对应的 .c 文件生成。% 是通配符,表示任意匹配的文件名。
多个Makefile文件
- 若同一目录下需要多个makefile文件 ,可以给makefile分别取名叫:makefile1 makefile2,
- 终端输入make,默认执行Makefile文件。若提示没有make指令,则用下列命令安装
sudo apt-get install make
- 可以使用 make -f 指定使用哪个 Makefile 来构建项目
make -f Makefile.debug//编译调试版本 make -f Makefile.release//编译发布版本
- 在主 Makefile 中包含其他 Makefile
- 使用 include 语法包含其他 Makefile。这样可以让你在一个 Makefile 中组织多个构建规则。# 主 Makefile include Makefile.debug include Makefile.release # 你可以根据目标调用不同的构建规则 .PHONY: all all: debug release debug: $(MAKE) -f Makefile.debug release: $(MAKE) -f Makefile.release clean: $(MAKE) -f Makefile.debug clean $(MAKE) -f Makefile.release clean
- 根据环境变量选择不同的 Makefile
- 通过设置环境变量来选择使用哪个 Makefile。这是根据构建时的条件或参数来决定使用哪个规则文件的策略。
# 主 Makefile ifeq ($(BUILD_TYPE), debug) #GNU Make 中的条件判断语法 判断BUILD_TYPE == debug include Makefile.debug else ifeq ($(BUILD_TYPE), release) include Makefile.release endif .PHONY: all all: $(MAKE) -f $(MAKEFILE) clean: $(MAKE) -f $(MAKEFILE) clean
- BUILD_TYPE 是一个变量,它决定了当前的构建模式。可以在命令行或环境中设置这个变量来控制构建流程
make BUILD_TYPE=debug//包含 Makefile.debug make BUILD_TYPE=release//包含 Makefile.release
赋值
- 变量引用:makefile中的变量与shell脚本一致,不需要定义,也没有数据类型的说法。$(变量名)
- “=” 最终赋值
- 会将变量在makefile中的所有赋值都完成后,将最后一次的赋值当做结果赋值给变量。
- 使用 = 进行赋值时,变量的值在实际使用时才被解析(延迟计算)。 - ":="立即赋值
- 变量的值在赋值时立即展开
- 与 = 不同的是,:= 不会在使用时重新计算变量的值,而是在赋值时就确定下来。
SRC = $(wildcard *.c)
OBJS := $(patsubst %.c,%.o,$(SRC))
# OBJS 的值在定义时就被确定下来
# SRC 的值仍然会在每次使用时重新计算。
- 条件赋值 (?=)
条件赋值使用 ?=,它的意思是:如果变量没有被定义过,则进行赋值,否则保留已有的值。通常用于给变量提供默认值,用户可以通过命令行传递参数覆盖默认值。
CC ?= gcc
CFLAGS ?= -O2
#如果 CC 和 CFLAGS 没有被定义,则使用默认的 gcc 和 -O2,否则保留外部传递的值。
使用外部传递变量
make CC=clang
- "+="追加赋值
追加赋值使用 +=,它用于将值追加到已有的变量中,特别是在扩展编译选项或文件列表时非常有用
CFLAGS = -Wall
CFLAGS += -g
#CFLAGS 的最终值是 -Wall -g,+= 用于将 -g 追加到原有的 CFLAGS 变量中
简化Makefile
- 通过变量赋值进行简化
# 定义对象文件变量 Obj
Obj := 06_union.o 07_func.o # Obj 变量包含 06_union.o 和 07_func.o 两个对象文件
# 定义目标文件变量 Target
Target := a.out # Target 变量表示最终生成的可执行文件 a.out
# 定义编译器变量 CC
CC := gcc # CC 变量定义为 gcc 编译器
# 定义编译选项变量 CAN
CAN := -c -o # CAN 变量表示编译选项,用于生成目标文件
# 规则:生成最终可执行文件
$(Target): $(Obj) # 目标 a.out 依赖于所有对象文件 $(Obj)
$(CC) $(Obj) -o $(Target) # 使用 gcc 编译器将所有对象文件链接生成 a.out
# 规则:生成 06_union.o 对象文件
06_union.o: 06_union.c # 目标 06_union.o 依赖于源文件 06_union.c
$(CC) 06_union.c $(CAN) 06_union.o # 使用 gcc 编译器将 06_union.c 编译成 06_union.o
# 规则:生成 07_func.o 对象文件
07_func.o: 07_func.c # 目标 07_func.o 依赖于源文件 07_func.c
$(CC) 07_func.c $(CAN) 07_func.o # 使用 gcc 编译器将 07_func.c 编译成 07_func.o
# 伪目标:clean,用于清理生成文件
.PHONY: clean # 定义伪目标 clean,不会生成与其同名的文件
clean: # clean 目标,用于删除生成的对象文件和可执行文件
rm $(Obj) $(Target) # 删除对象文件 $(Obj) 和最终生成的可执行文件 $(Target)
- 用特殊符号简化
$@ 目标文件
$^ 所有依赖文件
$< 第一个依赖文件
# 定义目标文件的变量
obj := union.o func.o
# 定义最终生成的可执行文件的名称
Target := a.out
# 指定编译器为gcc
CC := gcc
# 指定编译选项 -c 表示只编译,不进行链接
CFLAGS := -c
# 规则:生成可执行文件
# $@ 表示目标文件,这里是 $(Target)
# $^ 表示所有的依赖文件,这里是 $(obj)
$(Target): $(obj)
$(CC) -o $@ $^
# 规则:生成 union.o 文件
# union.o 依赖 union.c 和 func.h
# $< 表示第一个依赖文件,这里是 union.c
union.o: union.c func.h
$(CC) $(CFLAGS) union.c
# 规则:生成 func.o 文件
# func.o 依赖 func.c 和 func.h
# $< 表示第一个依赖文件,这里是 func.c
func.o: func.c func.h
$(CC) $(CFLAGS) func.c
# 规则:清理生成的文件
# rm -f 强制删除目标文件 $(obj) 和 $(Target)
clean:
rm -f $(obj) $(Target)
- Makefile最终版
# 定义变量
obj := union.o func.o # 目标文件列表,包含 union.o 和 func.o
Target := a.out # 最终生成的可执行文件名称
CC := gcc # 使用 gcc 作为编译器
CFLAGS := -c # 编译选项,-c 表示只编译,不进行链接
# 生成可执行文件的规则
# $(Target): $(obj) 表示生成 $(Target) 需要依赖 $(obj)
# $@ 表示目标文件,这里是 $(Target)
# $^ 表示所有的依赖文件,这里是 $(obj)
$(Target): $(obj)
$(CC) -o $@ $^
# 生成 union.o 的规则
# union.o 依赖 union.c 和 func.h
# $< 表示第一个依赖文件,这里是 union.c
union.o: union.c func.h
$(CC) $(CFLAGS) $<
# 生成 func.o 的规则
# func.o 依赖 func.c 和 func.h
# $< 表示第一个依赖文件,这里是 func.c
func.o: func.c func.h
$(CC) $(CFLAGS) $<
# 清理生成的文件
# clean 是一个伪目标,不生成文件,只执行命令
# rm -f 强制删除 $(obj) 和 $(Target)
clean:
rm -f $(obj) $(Target)