学习资料:
makefile官方文档https://www.gnu.org/software/make/manual/make.html
陈皓大佬的《跟我一起写Makefile》
学习笔记:
Makefile 由一组规则组成。规则通常如下所示:
command
command
command
- 目标是文件名,用空格分隔。通常,每个规则只有一个。
- 这些命令是一系列步骤,通常用于创建目标。这些需要以制表符开头,而不是空格。
- 先决条件也是文件名,用空格分隔。在运行目标的命令之前,这些文件需要存在。这些也称为依赖关系
all: target1 target2 target3
target1:
# 编译规则1
target2:
# 编译规则2
target3:
# 编译规则3
all被设置为第一个目标,并且target1、target2和target3被列为all的依赖。当你在命令行中运行make时,make命令会寻找并执行all目标规则,这将依次执行target1、target2和target3的编译规则。
因此,通过在Makefile中设置all作为默认目标规则,你可以简化构建过程,只需运行make命令即可执行整个编译过程,无需显式指定目标
变量
$/符号表示取变量的值,当变量名多于一个字符时,使用"( )"
$符的其他用法
$^ 表示所有的依赖文件
$@ 表示生成的目标文件
$< 代表第一个依赖文件
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
ALL: hello.out
hello.out: $(OBJ)
gcc $< -o $@
%.o: %.c
gcc -c $< -o $@
- 定义变量SRC,用于存储所有的.c源文件;
- 定义变量OBJ,用于存储所有的.o目标文件;
- 定义默认目标ALL,依赖于hello.out;
- 定义目标hello.out,依赖于$(OBJ),即所有的.o目标文件;
- 定义模式规则%.o: %.c,表示将.c源文件编译成对应的.o目标文件。
wildcard *.c
是 Makefile 中的一个通配符,用于匹配当前目录下所有扩展名为.c
的文件。它的作用是将匹配到的文件名列表赋值给变量SRC
。
patsubst
是 Makefile 中的一个模式替换函数,用于将匹配到的模式进行替换。具体来说,$(patsubst %.c, %.o, $(SRC))
这个表达式的作用是将变量SRC
中所有扩展名为.c
的文件名替换为相应的.o
文件名。换句话说,它会将所有的源文件(.c
文件)转换为对应的目标文件(.o
文件)。
Makefile 中支持的通配符:
*
:匹配任意长度的任意字符,可以出现在文件名的任何位置。例如,*.c
会匹配所有以.c
结尾的文件。?
:匹配任意一个字符。这在指定依赖关系时特别有用,$?
表示检查目标依赖列表中已更新的文件。[...]
:用于匹配方括号内指定的任意一个字符。例如,[abc]
会匹配文件名中包含a
、b
或c
的任意文件。
Makefile函数:
Makefile中的函数用于处理字符串、文件路径、以及执行系统命令:
- 文本处理函数:
subst
:替换字符串中的一部分。patsubst
:模式替换,用于替换符合特定模式的字符串。findstring
:查找子串在字符串中的位置。filter
:过滤列表中符合或不符合模式的元素。filter-out
:反向过滤,即过滤出不符合模式的元素。sort
:对字符串进行排序。
- 文件名处理函数:
wildcard
:获取匹配特定模式的文件列表。dir
:获取文件的目录部分。notdir
:获取不带路径的文件名。basename
:获取路径的基本名称,即最后的文件名或目录名。abspath
:获取文件的绝对路径。dirname
:获取文件的目录路径。
- 调用系统命令的函数:
shell
:执行shell命令并返回结果。call
:执行一个make命令。
- 控制结构函数:
if
…else
…endif
:条件判断。foreach
…endforeach
:循环遍历。
变量赋值
1、"="是最普通的等号,在Makefile中容易搞错赋值等号,使用 “=”进行赋值,变量的值是整个Makefile中最后被指定的值。
VIR_A = A
VIR_B = $(VIR_A) B
VIR_A = AA
经过上面的赋值后,最后VIR_B的值是AA B,而不是A B,在make时,会把整个Makefile展开,来决定变量的值
2、“:=” 表示直接赋值,赋予当前位置的值。
VIR_A := A
VIR_B := $(VIR_A) B
VIR_A := AA
最后BIR_B的值是A B,即根据当前位置进行赋值。因此相当于“=”,“:=”才是真正意义上的直接赋值
3.“?=” 表示如果该变量没有被赋值,赋值予等号后面的值。
VIR ?= new_value
如果VIR在之前没有被赋值,那么VIR的值就为new_value。
VIR := old_value
VIR ?= new_value
这种情况下,VIR的值就是old_value
4、**“+=”**和平时写代码的理解是一样的,表示将符号后面的值添加到前面的变量上
Makefile预定义变量
是一些在执行make命令时自动设置的变量,它们提供了有关当前构建环境的信息。以下是一些常见的Makefile预定义变量:
$@
:表示规则中的目标文件名。$<
:表示规则中的第一个依赖文件名。$^
:表示规则中的所有依赖文件名,以空格分隔。$?
:表示规则中所有比目标文件更新的依赖文件名,以空格分隔。$*
:表示规则中的目标文件名,但不包含扩展名。$(@D)
:表示目标文件所在的目录路径。$(@F)
:表示目标文件的文件名,不包含目录路径。$(<D)
:表示第一个依赖文件所在的目录路径。$(<F)
:表示第一个依赖文件的文件名,不包含目录路径。$(^D)
:表示所有依赖文件所在的目录路径,以空格分隔。$(^F)
:表示所有依赖文件的文件名,不包含目录路径,以空格分隔。$(?D)
:表示所有比目标文件更新的依赖文件所在的目录路径,以空格分隔。$(?F)
:表示所有比目标文件更新的依赖文件的文件名,不包含目录路径,以空格分隔。
嵌套执行Makefile
在一些大工程中,会把不同模块或不同功能的源文件放在不同的目录中,我们可以在每个目录中都写一个该目录的Makefile这有利于让我们的Makefile变的更加简洁,不至于把所有东西全部写在一个Makefile中。
列如在子目录subdir目录下有个Makefile文件,来指明这个目录下文件的编译规则。外部总Makefile可以这样写
subsystem:
cd subdir && $(MAKE)
其等价于:
subsystem:
$(MAKE) -C subdir
定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较有利于维护。两个例子意思都是先进入"subdir"目录,然后执行make命令
我们把这个Makefile叫做总控Makefile,总控Makefile的变量可以传递到下级的Makefile中,但是不会覆盖下层Makefile中所定义的变量,除非指定了 "-e"参数。
如果传递变量到下级Makefile中,那么可以使用这样的声明
export
如果不想让某些变量传递到下级Makefile,可以使用
unexport
export variable = value
等价于
variable = value
export variable
等价于
export variable := value
等价于
variable := value
export variable
如果需要传递所有变量,那么只要一个export就行了。后面什么也不用跟,表示传递所有变量
Makefile宏
Makefile中的宏类似于变量,用于给一组值命名,以便在Makefile中重复使用。
定义宏: 在Makefile中定义宏通常使用:=
或?=
操作符。:=
是直接赋值,而?=
是在变量未被赋值时使用默认值。例如:
CFLAGS := -O2 -Wall
这里定义了一个名为CFLAGS的宏,它代表了编译选项。
使用宏: 在Makefile的规则中,可以通过$(MACRO_NAME)
的形式来使用宏。例如,如果有一个名为CFLAGS
的宏,可以在编译命令中使用它:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
在这个例子中,$(CFLAGS)
会被替换为之前定义的编译选项。
宏的作用域: 宏的作用域通常是全局的,除非它们在目标的上下文中被重新定义。这意味着一旦宏被定义,它可以在整个Makefile文件中使用,直到被重新定义或者Makefile文件结束。
总的来说,Makefile中的宏是一种强大的工具,可以帮助简化构建规则,提高代码的可读性和可维护性。通过合理地使用宏,可以使得Makefile更加简洁和灵活。
指定头文件路径
一般都是通过"-I"(大写i)来指定,假设头文件在:
/home/develop/include
则可以通过-I指定:
-I/home/develop/include
将该目录添加到头文件搜索路径中
在Makefile中则可以这样写:
CFLAGS=-I/home/develop/include
然后在编译的时候,引用CFLAGS即可,如下
your app:*.c
gcc $(CFLAGS) -o yourapp
指定库文件路径
与上面指定头文件类似只不过使用的是"-L"来指定
LDFLAGS=-L/usr/lib -L/path/to/your/lib
1
告诉链接器要链接哪些库文件,使用"-l"(小写L)如下:
LIBS = -lpthread -liconv
困惑讲解
在Makefile中,$@是一个特殊的变量,它代表当前规则的目标文件名。当执行make命令时,Makefile会根据目标文件名来查找对应的规则,并执行该规则的命令。
下面是一个示例的Makefile代码:
# 定义一个目标文件名为output.txt的规则
output.txt: input.txt
# 使用echo命令将input.txt的内容输出到output.txt
echo "This is the content of input.txt" > $@
# 定义一个目标文件名为clean的规则
.PHONY: clean
# 使用rm命令删除output.txt文件
clean:
rm output.txt
在这个例子中,我们定义了两个规则:output.txt
和clean
。
output.txt
规则的目标是生成一个名为output.txt
的文件。它的依赖文件是input.txt
。在执行这个规则时,会执行echo "This is the content of input.txt" > $@
命令,其中$@
表示当前规则的目标文件名,即output.txt
。这条命令的作用是将字符串"This is the content of input.txt"输出到output.txt
文件中。clean
规则是一个伪目标(.PHONY),它没有实际的文件作为目标。执行这个规则时,会执行rm output.txt
命令,用于删除output.txt
文件。
通过使用$@变量,我们可以方便地引用当前规则的目标文件名,从而实现对不同目标文件的操作。
echo:会在shell中显示echo这条命令和这条命令的输出结果
@echo:不会在shell中显示echo这条命令,但是会显示命令的输出结果
例如:
echo_test:
echo "hello world"
echo_test_with_address:
@echo "hello world"
输出
echo test的输出结果为:
echo "hello world"
hello world
echo test with address的输出结果为:
hello world