文章目录
前言
makefile文件是用来管理项目工程文件的,通过执行make命令,make就会解析并执行makefile文件。
使用 gcc 的命令进行程序编译时,对于单个文件或者同一路径下的少数文件编译是比较方便的,但是对项目中有很多文件,甚至这些文件不在同一个文件夹下,则必须使用 make 。
若当前目录下有写好的 makefile,则直接在当前目录下运行 make 即可。
文件名:makefile或Makefile
makefile 基本规则
# 每条规则的语法格式:
target1,target2...: depend1, depend2, ...
command
......
......
# 命令可以是多个,每个命令前必须有一个Tab缩进并且独占占一行
每条规则由三个部分组成分别是目标文件(target),依赖文件(depend) 和命令(command)。
- 命令: 通过执行什么命令由依赖文件生成目标文件
- 依赖: 目标文件由哪些文件生成
- 目标:要生成的目标文件
下面是一些简单的例子:
# 举例: 有源文件a.c b.c c.c d.c head.h, 需要生成可执行文件test
################# 例1 #################
test:a.c b.c c.c
gcc a.c b.c c.c -o test
################# 例2 #################
# 有多个目标, 多个依赖, 多个命令
test, test1: a.c b.c c.c d.c
gcc a.c b.c -o test
gcc c.c d.c -o test1
################# 例3 #################
# 规则之间的嵌套
# 优点是,若有的依赖文件没更新,则不会去重新编译它
test:a.o b.o c.o
gcc a.o b.o c.o -o test
a.o:a.c
gcc -c a.c -o a.o
b.o:b.c
gcc -c b.c -o b.o
c.o:c.c
gcc -c c.c -o c.o
自动推导
main: main.o func1.o func2.o
gcc main.o func1.o func2.o -o main
可以发现上边的 makefile 文件中只有一条规则,依赖中所有的 .o 文件在本地项目目录中是不存在的,并且也没有其他的规则用来生成这些依赖文件,这时候 make 会使用内部默认的构造规则先将这些依赖文件生成出来,然后在执行规则中的命令,最后生成目标文件。
# make命令默认找makefile文件
# 也可以换个别名,但make要加-f选项
mv makefile mymake
make -f mymake
makefile 工作原理
make 会首先找到 makefile 文件中的规则,分析并执行相应的指令。
如果有的依赖文件不存在,则向下搜索规则,看是否有生成该依赖文件的规则:如果有规则,则执行生成依赖文件;如果没有则报错。
如果所有的依赖文件都存在,检查规则中的目标是否需要更新,必须先检查它的所有依赖文件,依赖文件中有任何一个被更新,则目标必须更新。
- 目标时间戳 > 所有依赖的时间戳:规则中的命令不会被执行
- 目标时间戳 < 某些依赖的时间戳:规则中的命令会被执行
如果不依赖任何条件(伪目标),则命令都会被执行
makefile 变量
上述例3的程序重复的很多,若文件很多,则必须要写出很多重复的语句,显得很冗余,所以,要采用 makefile 中的变量优化。
普通变量
自定义变量
# 创建一个变量名并且给其赋值
变量名=变量值
# 查看变量的值
$(变量名)
# 定义变量并赋值
obj=main.o func1.o func2.o
# 取变量的值
$(obj)
# 例
obj=main.o func1.o func2.o
tar=main
$(tar):$(obj)
gcc $(obj) -o $(tar)
预定义变量
变量名 | 含义 | 值 |
---|---|---|
CC | C 编译器的名称 | cc |
CXX | C++ 编译器的名称 | g++ |
CPPFLAGS | C预处理选项 | -I |
RM | 删除文件程序的名称 | rm -f |
CFLAGS | C 编译器的编译选项 | -Wall -g -c |
LDFLAGS | 链接器选项 | -L -l |
# 这是一个规则,普通写法
main: main.o func1.o func2.o
gcc main.o func1.o func2.o -o main
# 使用了自定义变量和预定义变量
obj=main.o func1.o func2.o
target=main
CFLAGS=-O3 #代码优化
$(target):$(obj)
$(CC) $(obj) -o $(target) $(CFLAGS)
自动变量
自动变量用来代表这些规则中的目标文件和依赖文件,并且它们只能在规则的命令中使用。
变量 | 含义 |
---|---|
$@ | 表示规则中的目标 |
$< | 表示规则中的第一个条件 |
$^ | 表示规则中的所有条件,组成一个列表,以空格隔开,并且会自动去重 |
$* | 表示目标的名称,不包含目标的扩展名 |
# 普通写法
main: main.o func1.o func2.o
gcc main.o func1.o func2.o -o main
# 使用自动变量
main: main.o func1.o func2.o
gcc $^ -o $@
makefile 模式匹配
还是以例3为例,从第二个规则开始到第四个规则做的是相同的事情,但是由于文件名不同不得不在文件中写出多个规则,这就让 makefile 文件看起来非常的冗余,我们可以将这一系列的相同操作整理成一个模板
# 表示任意的 .c 和 .o 文件
%.c, %.o
# 表示所有的 .c 和 .o 文件
*.c, *.o
a.o:a.c
gcc -c a.c -o a.o
b.o:b.c
gcc -c b.c -o b.o
c.o:c.c
gcc -c c.c -o c.o
# 简写后
%.c: %.o
gcc $< -c $@
makefile 函数
makefile中有很多函数并且所有的函数都是有返回值的。
makefile 中函数的格式: $(函数名 参数1, 参数2, 参数3, ...)
,主要目的是让我们能够快速方便的得到函数的返回值。
wildcard函数
获取指定目录下指定类型的文件名
其返回值是以空格分割的所有符合条件的文件名列表
$(wildcard PATTERN...)
# 参数: 指定某个目录, 搜索这个路径下指定类型的文件,比如:*.c
# 返回值:得到的若干个文件的文件列表,文件名之间使用空格间隔
# 例如
src=$(wildcard /home/duan/*.c *.c)
# 返回值格式:
src=/home/duan/aa.c /home/duan/bb.c a.c b.c c.c
patsubst函数
按照指定的模式替换指定的文件名的后缀
# 有三个参数, 参数之间使用逗号间隔
$(patsubst <pattern>,<replacement>, <text>)
# 参数功能:
pattern: 指出要被替换的文件名中的后缀是什么, 比如:%.c
replacement: 指定参数pattern中的后缀最终要被替换为什么, 比如:%.o
text: 该参数中存储这要被替换的原始数据
# 返回值:
函数返回被替换过后的字符串
# 例如将所有的.cpp替换成.o
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 清理操作
# 通过执行规则中的命令,可以只执行一个动作,不生成任何文件,这样的目标被称为伪目标
# 在makefile中声明一个伪目标需要使用.PHONY关键字
.PHONY:伪目标名称
# 伪目标:执行下面命令时没有生成目标文件
# 如果直接这样写,clean就是makefile的基本规则,就会检查依赖文件,此时依赖文件是空,相当于都是最新的,所以输入make clean的时候不会执行
# 另外,如果当前目录下有clean文件,则make clean也不会执行对应的命令
clean:
rm -rf *.o
@rm -rf *.o #不显示命令本身
# 综合
target=main
src=$(wildcard *.c)
obj=$(patsubst %.c,%.o, $(src))
CC=gcc
CPPFLAGS=-I./
$(target): $(obj)
$(CC) -o $@ $^
%.o: %.c
$(CC) -c $< $(CPPFLAGS)
.PHONY:clean
clean:
rm $(obj) $(target)
makefile 练习
版本1
calc:add.c div.c main.c mult.c sub.c
gcc add.o div.o main.o mult.o sub.o -o calc
# 优点:书写简单
# 缺点:只要依赖中的某一个源文件被修改,所有的源文件都需要被重新编译,太耗时、效率低
版本2
# 默认所有的依赖都不存在, 需要使用其他规则生成这些依赖
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.o:add.c
gcc -c add.c -o add.o
div.o:div.c
gcc -c div.c -o div.o
main.o:main.c
gcc -c main.c -o main.o
sub.o:sub.c
gcc -c sub.c -o sub.o
mult.o:mult.c
gcc -c mult.c -o mult.o
# 优点:相较于版本 1 效率提升了
# 缺点:规则比较冗余,需要精简
版本3
# 添加自定义变量
obj=add.o div.o main.o mult.o sub.o
tar=calc
$(tar):$(obj)
gcc $(obj) -o $(tar)
%.o:%.c
gcc -c $< -o $@
# 优点:文件精简不少,变得简洁了
# 缺点:变量 obj 的值需要手动的写出来,如果需要编译的项目文件很多,都用手写出来不现实
版本4
# 使用函数搜索当前目录下的源文件 .c
src=$(wildcard *.c)
# 将源文件的后缀替换为 .o
obj=$(patsubst %.c, %.o, $(src))
tar=calc
$(tar):$(obj)
gcc $(obj) -o $(tar)
%.o:%.c
gcc -c $< -o $@
# 优点:解决了自动加载项目文件的问题,解放了双手
# 缺点:没有文件删除的功能,不能删除项目编译过程中生成的目标文件(*.o)和可执行程序
版本5
src=$(wildcard *.c)
obj=$(patsubst %.c, %.o, $(src))
tar=calc
$(tar):$(obj)
gcc $(obj) -o $(tar)
%.o:%.c
gcc -c $< -o $@
# 这个规则比较特殊, clean根本不会生成, 这是一个伪目标
clean:
rm -rf $(obj) $(tar)
# 优点:添加了新的规则用于文件的删除,直接 make clean 就可以执行规则中的删除命令了
# 缺点:clean 会和文件夹下的文件名重名; 另外,由于没有依赖文件,所以 clean 永远是最新的文件,所以不会运行
版本6(最终版)
src=$(wildcard *.c)
obj=$(patsubst %.c, %.o, $(src))
tar=calc
$(tar):$(obj)
gcc $(obj) -o $(tar)
%.o:%.c
gcc -c $< -o $@
# 声明clean为伪文件
.PHONY:clean
clean:
# - 表示强制这个指令执行, 如果执行失败也不会终止;
# 如果不加,若命令执行失败,echo 语句是不会执行的
-rm -rf $(obj) $(target)
echo "hello, 我是测试字符串"
makefile 进阶题
# 目录结构
.
├── include
│ └── head.h ==> 头文件, 声明了加减乘除四个函数
├── main.c ==> 测试程序, 调用了head.h中的函数
└── src
├── add.c ==> 加法运算
├── div.c ==> 除法运算
├── mult.c ==> 乘法运算
└── sub.c ==> 减法运算
# 根据上边的项目目录结构编写的 makefile 文件如下:
tar = app
src=$(wildcard *.c ./src/*.c)
obj=$(patsubst %.c, %.o, $(src))
include=./include
$(tar):$(obj)
gcc $^ -o $@
%.o:%.c
gcc -c $< -o $@ -I$(include)
.PHONY:clean
clean:
rm -rf $(obj) $(tar)