一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本
一样,其中也可以执行操作系统的命令
。
Makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
make主要解决两个问题:
大量代码的关系维护
大项目中源代码比较多,手工维护、编译时间长而且编译命令复杂,难以记忆及维护;
把代码维护命令及编译命令写在makefile文件中,然后再用make工具解析此文件自动执行相应命令,可实现代码的合理编译.
减少重复编译时间
Makefile文件命名规则
makefile 或 Makefile 都可以,推荐后者。
make 工具的安装
sudo apt install make
make是一个命令工具,它解释Makefile中的指令(准确说是规则)。
make命令格式:
make [-f file] [option] [targets]
[-f file] :
- make默认在工作目录中寻找名为GNUmakefile、makefile、Makefile的文件作为makefile输入文件;
- -f 可以指定以上名字以外的文件作为makefile输入文件。
[optios] :
-v: 显示make工具的版本信息
-w: 在处理makefile之前和之后显示工作路径
-C dir: 读取makefile之前改变工作路径至dir目录
-n: 只打印要执行的命令但不执行
-s: 执行但不显示执行的命令
[targets] :
- 若使用make命令时没有指定目标,则make工具默认会实现makefile文件内的第一个目标,然后退出;
- 指定了make工具要实现的目标,目标可以是一个或多个(多个目标间用空格隔开)。
规则 :
目标:依赖文件列表
命令列表
Makefile 规则三要素 :
1)目标
2)依赖文件
3)命令
示例程序 :
test : test1 test2
echo "test1"
test1 :
echo test1
test2 :
echo test2
当使用make 解析 Makefile文件时,会执行以下动作:
下面从一个实例出发,看看一个优美简练的Makefile是如何编写的。
首先我们编写了几个.h和.c文件:add.h sub.h mul.h div.h
和add.c sub.c mul.c div.c main.c
,这些代码基本相同,下面给出add.h add.c main.c
的代码:
//add.h
#include
int add(int a, int b);
//add.c
#include
int add(int a, int b) {
return a + b;
}
//main.c
#include
#include "add.h"
#include "sub.h"
#include "mul.h"
#include "div.h"
int main() {
int a = 9, b = 3;
printf("x + y = %d\n", add(a, b));
printf("x - y = %d\n", sub(a, b));
printf("x * y = %d\n", mul(a, b));
printf("x / y = %d\n", div(a, b));
return 0;
}
我们先来写一个最简单的Makefile,就把它称作Makefile_v1
吧,如下所示:
main : add.c sub.c mul.c div.c main.c
gcc add.c sub.c mul.c div.c main.c -o main
这个版本有什么缺点呢?那就是修改一个文件,所有文件都会被再次编译一遍。
将.c 文件编译链接成可执行文件的过程中,会产生一个.o 目标文件。如果我们把这些.o 文件当作依赖文件 ,再修改某一个.c 文件时,只需要重新生成对应的依赖.o 文件即可。如下所示:
main : add.o sub.o mul.o div.o main.o
gcc add.o sub.o mul.o div.o main.o -o main
main.o : main.c
gcc -c main.c -o main.o
add.o : add.c
gcc -c add.c -o add.o
sub.o : sub.c
gcc -c sub.c -o sub.o
mul.o : mul.c
gcc -c mul.c -o mul.o
div.o : div.c
gcc -c div.c -o div.o
在Makefile中使用变量有点类似于C语言中的宏定义,使用该变量相当于内容替换,使用变量可以使Makefile易于维护,修改内容变得简单变量定义及使用。
自定义变量
1)定义变量方法:
变量名 = 变量值
2)引用变量:
$(变量名) 或 ${变量名}
【注】Makefile 中的变量名规则
我们来改改之前的Makefile:
#变量
OBJS = add.o sub.o mul.o div.o main.o
TARGET = main
$(TARGET):$(OBJS)
gcc $(OBJS) -o $(TARGET)
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
mul.o:mul.c
gcc -c mul.c -o mul.o
div.o:div.c
gcc -c div.c -o div.o
main.o:main.c
gcc -c main.c -o main.o
clean:
rm -rf $(OBJS) $(TARGET)
这样就减少了很多重复工作,变量
的意义正在于此,方便开发者对数据的使用;
可以看到文件末尾多了一行clean,之前介绍过目标可以是一个动作,生成clean不需要依赖
所以直接执行命令rm -rf $(OBJS) $(TARGET)
删除掉所有 .o 文件和可执行文件main。
自动变量
- $@:表示规则中的目标
- $<:表示规则中第一个依赖
- $^:表示规则中的所有依赖,以列表形式存在且用空格隔开,如果这个列表中有重复项则消除重复项。
注意:自动变量只能在规则的命令中使用
改写Makefile如下:
OBJS = add.o sub.o mul.o div.o main.o
TARGET = main
CC = gcc
$(TARGET):$(OBJS)
$(CC) $^ -o $@
add.o:add.c
$(CC) -c $< -o $@
sub.o:sub.c
$(CC) -c $< -o $@
mul.o:mul.c
$(CC) -c $< -o $@
div.o:div.c
$(CC) -c $< -o $@
main.o:main.c
$(CC) -c $< -o $@
clean:
rm -rf $(OBJS) $(TARGET)
可以看到Makefile再一次被简化了,我们可以发现有多条规则的命令相同但却重复写了多次,此时Makefile给我们提供的模式匹配就派上了用场。
模式规则
%.o:%.c
改写Makefile如下:
OBJS = add.o sub.o mul.o div.o main.o
TARGET = main
CC = gcc
$(TARGET):$(OBJS)
$(CC) $(OBJS) -o $(TARGET)
%.o:%.c
$(CC) -c $< -o $@
这个版本还存在一个问题,我们看第一句OBJS = add.o sub.o mul.o div.o main.o
,如果一个项目中的.o 非常多难到要手动一个个加上去吗?显然不现实,makefile提供的函数解决了这个问题。
makefile 中函数非常多,在这里给大家介绍两个最常用的。
- wildcard - 查找指定目录下的指定类型的文件
src = $(wildcard *.c) //找到当前目录下所有后缀为.c 的文件,赋值给src- patsubst - 匹配替换
obj = $(patsubst %.c, %.o, $(src)) //把 src 变量里所有后缀为.c 的文件替换成.o
makefile中的所有函数都是有返回值的。
改写Makefile如下:
SRC = $(wildcard *.c)
OBJS = $(patsubst %.c, %.o, $(SRC))
TARGET = main
CC = gcc
$(TARGET):$(OBJS)
$(CC) $^ -o $@
%.o:.c
$(CC) -c $< -o $@
clean:
rm -rf $(OBJS) $(TARGET)
这个Makefile看起来就很简洁了,但这还不是最终版本,因为此Makefile在make时可能会出现一些问题,请接着阅读。
clean
用途:清除编译生成的中间.o 文件和最终目标文件
make clean
如果当前目录下存在同名clean文件,则不执行clean对应的命令,解决方案如下:
- 伪目标声明:.PHONY:clean
声明目标为伪目标之后,makefile将不会该判断目标是否存在或者该目标是否需要更新;- clean命令中的特殊符号:
- “-”此条命令出错,make也会继续执行后续的命令。如:“-rm main.o”
- “@”不显示命令本身,只显示结果。如:“@echo clean done”
经过以上,Makefile最终版本来了:
SRC = $(wildcard *.c)
OBJS = $(patsubst %.c, %.o, $(SRC))
TARGET = main
CC = gcc
$(TARGET):$(OBJS)
$(CC) $^ -o $@
%.o:%.c
$(CC) -c $< -o $@
.PHONY:clean
clean:
rm -rf $(OBJS) $(TARGET)