1. Makefile学习笔记
1.1 makefile简介
1.1.1 概述
Makefile 是一种用于自动化构建软件的文件,它定义了一系列的规则和命令来编译和链接程序。
Makefile 通常与
make
工具一起使用,make
工具会读取 Makefile 文件中的指令,并按照其中的规则自动化地完成构建过程。其命名一般为makefile 或 Makefile
1.1.2 基本概念
makefile中的规则
一个Makefile文件中会定义一个或者多个规则,一个规则的格式如下:
目标:依赖…
< TAB > 命令(shell命令)
< TAB > …(后续的命令)
名词解释:
- 目标:这是一个文件或操作的名字。一般将定义了一个操作(一组命令的集合)的目标称为伪目标
- 依赖:目标文件所依赖的文件或者其他目标
- 命令:用来生成目标文件的实际命令(命令行前必须 Tab 缩进)
关于伪目标
伪目标(Phony Targets)是在 Makefile 中不会生成实际文件的目标,通常用于执行某些管理任务,如清理、中间文件的删除、运行测试等。伪目标的名字不对应于文件,而是一些操作指令 。
常见的伪目标:
clean:删除编译生成的文件。
all:编译所有目标文件。
install:安装编译生成的文件到指定位置。
test:运行测试用例。
.PHONY 的作用:
确保命令被执行,避免因文件名冲突导致命令不执行。如果当前目录下有一个与伪目标同名的文件,make
会优先认为这个文件是目标文件而不是伪目标,进而跳过命令而不执行。将目标声明为 .PHONY
后,即使存在与伪目标同名的文件,也会执行伪目标的命令,而不是根据文件的时间戳判断是否需要执行。
# 例如,目录结构如下:
.
├── a.cpp
├── a.out
├── clean
└── makefile# make文件内容如下:
# a.out:a.cpp
# g++ a.cpp -o a.out
# clean:
# rm a.out# 如果不使用.PHONY,执行make clean的命令行输出如下:
make: 'clean' is up to date.# 在makefile文件中添加:
# .PHONY:clean
# 之后,执行make clean的命令行输出如下:
rm a.out
包含伪目标的makefile示例
# 定义伪目标
.PHONY: all clean install test# 定义目标 all,依赖于 my_program
all: my_program# 定义目标 my_program,依赖于 source.o
my_program: source.ogcc -o my_program source.o# 定义目标 source.o,依赖于 source.c
source.o: source.cgcc -c source.c# 定义伪目标 clean,用于清理生成文件
clean:rm -f my_program source.o# 定义伪目标 install,用于安装程序
install:cp my_program /usr/local/bin# 定义伪目标 test,用于运行测试
test:./my_program --test
makefile中的注释
Makefile 中只有单行注释,没有多行注释。
注释以 #
开头,从 #
开始直到该行末尾均为注释内容。
例如:
# 这是一个注释
CC = gcc # 定义编译器
1.2 makefile中的变量
Makefile 中的变量用于简化和管理构建过程中的重复部分,提高可读性和可维护性。变量允许你在 Makefile 中定义值,并在多个地方引用这些值。
变量的使用,使得修改makefile变得更加容易,只需要修改变量定义,避免了在每个使用它的地方进行修改。
1.2.1 自定义变量
变量的定义格式
VariableName = value
详细来讲,makefile的赋值有四种形式:延迟展开、立即展开、条件赋值、追加赋值。
=
:延迟展开,变量在被引用时才进行赋值。这意味着变量的值是在使用时确定的,而不是在定义时确定的。
:=
:立即展开,变量在定义时立即展开并赋值。变量的值在定义时就被固定下来,不会受后续变量修改的影响。
?=
:条件赋值,只有在变量未定义时才进行赋值。如果变量已经被定义,则保持其原有值。
+=
:追加赋值,将新的值追加到已有值后面。如果变量之前没有定义,则相当于 =
。
示例如下:
# 延迟展开
DELAYED = $(IMMEDIATE)
IMMEDIATE = delayed_value# 立即展开
IMMEDIATE := $(IMMEDIATE)
# 此时使用了 IMMEDIATE 的值,其值为delayed_value
# 再次定义了 IMMEDIATE,将其值设为 immediate_value
IMMEDIATE := immediate_value# 条件赋值
CONDITIONAL ?= conditional_value
CONDITIONAL ?= new_conditional_value# 追加赋值
APPEND = initial
APPEND += appended_valueall:echo DELAYED: $(DELAYED)echo IMMEDIATE: $(IMMEDIATE)echo CONDITIONAL: $(CONDITIONAL)echo APPEND: $(APPEND)
上述makefile的输出为:
echo DELAYED: immediate_value
echo IMMEDIATE: immediate_value
echo CONDITIONAL: conditional_value
echo APPEND: initial appended_value
1.2.2 变量的使用
变量的引用格式
$(变量名)
使用$
来表示用于引用变量或表示自动化(预定义)变量
1.2.3 常用的自动化变量
$@
:目标文件名(target file)
$<
:第一个依赖文件的名字(first dependency)
$^
: 所有依赖文件的名字(会去重),使用空格分隔(all dependencies)。
MAKE
:make 程序本身的名字。
CURDIR
:当前目录的绝对路径。
MAKEFILE_LIST
:make 正在读取的所有 makefile 的名字列表。
1.2.4 常用的变量定义
CC
:指定编译器
CFLAGS
:指定 C 编译器标志
CXX
:指定 C++ 编译器
CXXFLAGS
:指定 C++ 编译器标志
LDFLAGS
: 指定链接器标志
LDLIBS
:指定链接的库文件
SRCS
:指定源文件列表
OBJS
:指定目标文件列表
INCLUDES
:指定包含目录
LIBS
:指定库目录和库文件
# Makefile 示例
CC = gcc
CFLAGS = -Wall -g
LDFLAGS = -lm
CPPFLAGS = -I/usr/local/include
SRCS = main.c util.c
OBJS = $(SRCS:.c=.o)
TARGET = myappall: $(TARGET)$(TARGET): $(OBJS)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^%.o: %.c$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@clean:rm -f $(OBJS) $(TARGET)
1.2.5 变量的匹配规则
%
通配符
%
Makefile 中最常用的通配符,用于匹配零个或多个字符,通常用于表示任意文件名部分。
# 所有 .o 文件依赖于对应的 .c 文件
%.o: %.c$(CC) $(CFLAGS) -c $< -o $@
*
通配符
*
通配符用来匹配零个或多个字符,在 Makefile 中不常用,在shell脚本中常用。
# 匹配零个或多个字符。
FILES = *.c
?
通配符
?
通配符用于匹配单个字符。
# 匹配名称为 file?.c 文件,
all:@echo file?.c
[ ... ]
通配符
[]
用于匹配方括号内的任意一个字符。
# 匹配 filea.txt、fileb.txt 和 filec.txt
file[abc].txt:@echo $@
[ ^ ... ]
通配符
[ ^ … ]用于匹配不在方括号内的任意一个字符。
# 匹配不以 a、b 或 c 结尾的 .txt 文件,例如 filed.txt、filee.txt
file[^abc].txt:@echo $@
1.3 makefile中的常用函数
patsubst
函数
patsubst
函数用于模式替换。
语法(注意逗号):
$(patsubst pattern,replacement,text)
示例:
# 将 src/ 目录下的 .c 文件名替换为 build/ 目录下的 .o 文件名。
SRC := src/a.c src/b.c src/c.c
OBJS := $(patsubst src/%.c, build/%.o, $(SRC))
wildcard
函数
wildcard
函数用于获取符合模式的文件列表。
语法:
$(wildcard pattern)
示例:
# 获取 src 目录下所有的 .c 文件
SRC := $(wildcard src/*.c)
shell
shell
函数用于运行 shell 命令并返回结果。
语法:
$(shell command)
示例:
FILES := $(shell ls *.c)
返回当前目录下的所有 .c
文件。
其他
dir
函数返回文件名的目录部分。
notdir
函数返回文件名的非目录部分。
basename
函数去掉文件名的扩展名部分。
addsuffix
函数用于在每个文件名后添加后缀。
addprefix
函数用于在每个文件名前添加前缀。
join
函数用于连接两组文件名。
filter
函数用于过滤符合模式的文件名。
filter-out
函数用于过滤不符合模式的文件名。
sort
函数用于对文件名排序并去重。
1.4 示例makefile文件
目录结构:
project/
├── src/
│ ├── main.c
│ ├── util.c
│ └── helper.c
├── include/
│ └── util.h
└── Makefile
makefile文件:
# 变量定义
CC = gcc
CFLAGS = -Wall -g -Iinclude
LDFLAGS = -lm
SRC_DIR = src # 源文件目录
BUILD_DIR = build # 编译中间文件目录
BIN_DIR = bin # 目标二进制文件目录
# 使用 wildcard 函数获取所有源文件
SRC = $(wildcard $(SRC_DIR)/*.c)
# 使用 patsubst 函数将源文件路径转换为目标文件路径
OBJS = $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRC))
TARGET = $(BIN_DIR)/myapp# 规则定义
all: $(TARGET)$(TARGET): $(OBJS)@mkdir -p $(BIN_DIR)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c@mkdir -p $(BUILD_DIR)$(CC) $(CFLAGS) -c $< -o $@clean:rm -rf $(BUILD_DIR) $(BIN_DIR)# 伪目标
.PHONY: all clean
关于@mkdir -p $(BIN_DIR)
的解释:
@ :@符号放在命令前面,表示在执行这个命令时不要打印出命令本身,这可以让输出更加简洁。
mkdir -p :mkdir 是创建目录的命令;-p 选项表示“父目录”,它的作用是:如果目录不存在,则创建目录。如果目录已经存在,不会报错。可以递归地创建必要的父目录。
$(BIN_DIR):这是一个 Makefile 变量# 这个命令的意思是在执行目标规则时,确保 bin 目录存在。如果目录不存在,它会创建目录。如果目录已经存在,不会有任何影响。由于前面加了 @,这个命令本身不会被打印到控制台输出中。
1.5 参考内容
本笔记参考了以下内容:
- https://www.nowcoder.com/courses/cover/live/504
- https://blog.csdn.net/qq_53099212/article/details/132452987