makefile中的变量分为三种:自定义变量,预定义变量和自动变量。
用户可以定义自己的变量,称为用户自定义变量。makefile 中的变量是没有类型的,直接创建变量然后给其赋值就可以了。
# 错误, 只创建了变量名, 没有赋值
变量名
# 正确, 创建一个变量名并且给其赋值
变量名=变量值
# 如果将变量的值取出?
$(变量的名字)
# 举例 add.o div.o main.o mult.o sub.o
# 定义变量并赋值
obj=add.o div.o main.o mult.o sub.o
# 取变量的值
$(obj)
# 这是一个规则,普通写法
calc:add.o div.o main.o mult.o sub.o
gcc add.o div.o main.o mult.o sub.o -o calc
# 这是一个规则,里边使用了自定义变量
obj=add.o div.o main.o mult.o sub.o
target=calc
$(target):$(obj)
gcc $(obj) -o $(target)
在 Makefile 中有一些已经定义的变量,用户可以直接使用这些变量,不用进行定义。在进行编译的时候,某些条件下 Makefile 会使用这些预定义变量的值进行编译。这些预定义变量的名字一般都是大写的,经常采用的预定义变量如下表所示:
# 这是一个规则,普通写法
calc:add.o div.o main.o mult.o sub.o
gcc add.o div.o main.o mult.o sub.o -o calc
# 这是一个规则,里边使用了自定义变量和预定义变量
obj=add.o div.o main.o mult.o sub.o
target=calc
CFLAGS=-O3 # 代码优化
$(target):$(obj)
$(CC) $(obj) -o $(target) $(CFLAGS)
Makefile 中的变量除了用户自定义变量和预定义变量外,还有一类自动变量。Makefile 中的规则语句中经常会出现目标文件和依赖文件,自动变量用来代表这些规则中的目标文件和依赖文件,并且它们只能在规则的命令中使用。
# 这是一个规则,普通写法
calc:add.o div.o main.o mult.o sub.o
gcc add.o div.o main.o mult.o sub.o -o calc
# 这是一个规则,里边使用了自定义变量
# 使用自动变量, 替换相关的内容
calc:add.o div.o main.o mult.o sub.o
gcc $^ -o $@ # 自动变量只能在规则的命令中使用
先看一下之前的基础版
calc:add.o div.o main.o mult.o sub.o
gcc add.o div.o main.o mult.o sub.o -o calc
# 语法格式重复的规则, 将 .c -> .o, 使用的命令都是一样的 gcc *.c -c
add.o:add.c
gcc add.c -c
div.o:div.c
gcc div.c -c
main.o:main.c
gcc main.c -c
sub.o:sub.c
gcc sub.c -c
mult.o:mult.c
gcc mult.c -c
在阅读过程中能够发现从第二个规则开始到第六个规则做的是相同的事情, 但是由于文件名不同不得不在文件中写出多个规则,这就让 makefile 文件看起来非常的冗余,我们可以将这一系列的相同操作整理成一个模板,所有类似的操作都通过模板去匹配 makefile 会因此而精简不少,只是可读性会有所下降。
这个规则模板可以写成下边的样子,这种操作就称之为模式匹配。
# 模式匹配 -> 通过一个公式, 代表若干个满足条件的规则
# 依赖有一个, 后缀为.c, 生成的目标是一个 .o 的文件, % 是一个通配符, 匹配的是文件名
%.o:%.c
gcc $< -c
makefile中有很多函数并且**所有的函数都是有返回值的**。makefile中函数的格式和C/C++中函数也不同,其写法是这样的: $(函数名 参数1, 参数2, 参数3, …),主要目的是让我们能够快速方便的得到函数的返回值。
这里为大家介绍两个 makefile 中使用频率比较高的函数:wildcard和patsubst。
这个函数的主要作用是获取指定目录下指定类型的文件名,其返回值是以空格分割的、指定目录下的所有符合条件的文件名列表。函数原型如下:
# 该函数的参数只有一个, 但是这个参数可以分成若干个部分, 通过空格间隔
$(wildcard PATTERN...)
参数: 指定某个目录, 搜索这个路径下指定类型的文件,比如: *.c
参数功能:
返回值:
函数使用举例:
# 使用举例: 分别搜索三个不同目录下的 .c 格式的源文件
src = $(wildcard /home/robin/a/*.c /home/robin/b/*.c *.c) # *.c == ./*.c
# 返回值: 得到一个大的字符串, 里边有若干个满足条件的文件名, 文件名之间使用空格间隔
/home/robin/a/a.c /home/robin/a/b.c /home/robin/b/c.c /home/robin/b/d.c e.c f.c
这个函数的功能是按照指定的模式替换指定的文件名的后缀, 函数原型如下:
# 有三个参数, 参数之间使用 逗号间隔
$(patsubst ,,)
参数功能:
函数使用举例:
src = a.cpp b.cpp c.cpp e.cpp
# 把变量 src 中的所有文件名的后缀从 .cpp 替换为 .o
obj = $(patsubst %.cpp, %.o, $(src))
# obj 的值为: a.o b.o c.o e.o
下面基于一个简单的项目, 为大家演示一下编写一个makefile从不标准到标准的进化过程。
# 项目目录结构
.
├── add.c
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c
# 需要编写makefile对该项目进行自动化编译
calc:add.c div.c main.c mult.c sub.c
gcc add.c div.c main.c mult.c sub.c -o calc
# 默认所有的依赖都不存在, 需要使用其他规则生成这些依赖
# 因为 add.o 被更新, 需要使用最新的依赖, 生成最新的目标
calc:add.o div.o main.o mult.o sub.o
gcc add.o div.o main.o mult.o sub.o -o calc
# 如果修改了add.c, add.o 被重新生成
add.o:add.c
gcc add.c -c
div.o:div.c
gcc div.c -c
main.o:main.c
gcc main.c -c
sub.o:sub.c
gcc sub.c -c
mult.o:mult.c
gcc mult.c -c
# 添加自定义变量 -> makefile中注释前 使用 #
obj=add.o div.o main.o mult.o sub.o
target=calc
$(target):$(obj)
gcc $(obj) -o $(target)
%.o:%.c
gcc $< -c
# 添加自定义变量 -> makefile中注释前 使用 #
# 使用函数搜索当前目录下的源文件 .c
src=$(wildcard *.c)
# 将源文件的后缀替换为 .o
# % 匹配的内容是不能被替换的, 需要替换的是第一个参数中的后缀, 替换为第二个参数中指定的后缀
# obj=$(patsubst %.cpp, %.o, $(src)) 将src中的关键字 .cpp 替换为 .o
obj=$(patsubst %.c, %.o, $(src))
target=calc
$(target):$(obj)
gcc $(obj) -o $(target)
%.o:%.c
gcc $< -c
# 添加自定义变量 -> makefile中注释前 使用 #
# 使用函数搜索当前目录下的源文件 .c
src=$(wildcard *.c)
# 将源文件的后缀替换为 .o
obj=$(patsubst %.c, %.o, $(src))
target=calc
# obj 的值 xxx.o xxx.o xxx.o xx.o
$(target):$(obj)
gcc $(obj) -o $(target)
%.o:%.c
gcc $< -c
# 添加规则, 删除生成文件 *.o 可执行程序
# 这个规则比较特殊, clean根本不会生成, 这是一个伪目标
clean:
rm $(obj) $(target)
正常情况下这个版本的makefile是可以正常工作的,但是我们如果在这个项目目录中添加一个叫做clean的文件(和规则中的目标名称相同),再进行 make clean发现这个规则就不能正常工作了。
# 在项目目录中添加一个叫 clean的文件, 然后在 make clean 这个规则中的命令就不工作了
$ ls
add.c calc div.c head.h main.o mult.c sub.c
add.o div.o main.c makefile mult.o sub.o clean ---> 新添加的
# 使用 makefile 中的规则删除生成的目标文件和可执行程序
$ make clean
make: 'clean' is up to date.
# 查看目录, 发现相关文件并没有被删除, make clean 失败了
$ ls
add.c calc div.c head.h main.o mult.c sub.c
add.o clean div.o main.c makefile mult.o sub.o
这个问题的关键点在于 clean是一个伪目标, 不对应任何实体文件, 在前边讲关于文件时间戳更新问题的时候说过,如果目标不存在规则的命令肯定被执行, 如果目标文件存在了就需要比较规则中目标文件和依赖文件的时间戳,满足条件才执行规则的命令,否则不执行。(创建一个文件之后, 这个文件的时间戳一直在他之前)
解决这个问题需要在 makefile 中声明 clean是一个伪目标,这样 make 就不会对文件的时间戳进行检测,规则中的命令也就每次都会被执行了。
在 makefile 中声明一个伪目标需要使用 .PHONY 关键字, 声明方式为: .PHONY:伪文件名称
# 添加自定义变量 -> makefile中注释前 使用 #
# 使用函数搜索当前目录下的源文件 .c
src=$(wildcard *.c)
# 将源文件的后缀替换为 .o
obj=$(patsubst %.c, %.o, $(src))
target=calc
$(target):$(obj)
gcc $(obj) -o $(target)
%.o:%.c
gcc $< -c
# 添加规则, 删除生成文件 *.o 可执行程序
# 声明clean为伪文件
.PHONY:clean
clean:
# shell命令前的 - 表示强制这个指令执行, 如果执行失败也不会终止
-rm $(obj) $(target)
echo "hello, 我是测试字符串"