目录
什么是Makefile?
Makefile是软件开发中的自动化构建工具,它定义了如何将源代码转换为可执行程序的完整过程。就像厨师需要食谱来指导烹饪过程一样,开发者需要Makefile来指导构建过程。Makefile的核心是依赖关系和构建规则的集合。
在Linux/Unix开发环境中,Makefile通常与make命令配合使用,能够:
自动化编译过程
处理多文件项目的复杂依赖
只重新编译修改过的文件
提供统一的构建接口
为什么需要Makefile?
考虑以下开发场景:
项目包含数十个源文件,手动编译效率低下
源文件之间存在复杂依赖关系
需要为不同平台定制编译选项
构建过程包含预处理、编译、链接等多个步骤
Makefile完美解决了这些问题,它提供了:
自动化构建流程
智能的增量编译
可定制的构建规则
跨平台支持
依赖关系与依赖方法:
那么什么又是依赖方法呢?
把编程比作烹饪,想象你是一个厨师,要准备一顿晚餐。Makefile就像你的烹饪食谱,告诉你:
需要哪些食材(依赖关系)
如何加工这些食材(依赖方法)
最终要做出什么菜(目标)
做一盘西红柿炒蛋的依赖关系
makefile:
西红柿炒蛋: 切好的西红柿 打好的鸡蛋 炒好的菜
这表示:
要做出"西红柿炒蛋"这道菜(目标)
需要准备好"切好的西红柿"、"打好的鸡蛋"和"炒好的菜"(依赖)
对应到编程上,就可以表示为:
makefile:(表示makefile文件,无实际意义)
main.exe: main.o utils.o
这表示:
要生成main.exe程序(目标)
需要main.o和utils.o这两个中间文件(依赖)
做西红柿炒蛋的步骤
makefile:
切好的西红柿: 新鲜西红柿
把西红柿洗净切片
打好的鸡蛋: 鸡蛋
把鸡蛋打入碗中搅拌均匀
炒好的菜: 切好的西红柿 打好的鸡蛋
热油下锅,先炒蛋后加西红柿,加盐翻炒
西红柿炒蛋: 炒好的菜
装盘上菜
对应到编程:
makefile:
main.o: main.cpp
g++ -c main.cpp -o main.o
utils.o: utils.cpp
g++ -c utils.cpp -o utils.o
main.exe: main.o utils.o
g++ main.o utils.o -o main.exe
以一个简单的例子为例 ,在我们的linux系统下,我们有一个test.c文件,内容是:
#include<stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
接下来,如果我们想想运行这个.c文件让它产生可执行文件,是不是需要在命令行中输入:
gcc test.c -o test
但是,如果我们有多个文件,当我们修改了多个文件,每次都要输入一长串指令去编译,是不是就比较麻烦了。但是当我们有了Makefile文件后,就比较简单了:
、
这是我们的一个简单的Makefile,有了它之后,我们只需要在命令行简单输入一个make指令,就能完成gcc test.c -o test,我们输入make clean,就能自动完成rm -rf test,也就是删除操作。
删除:
在 Makefile 中,make
默认会执行 第一个目标(除非通过命令行指定其他目标)
所以直接运行
make
时,它会尝试构建test
(即执行gcc test.c -o test
)。如果
test.c
存在且test
不存在(或test.c
比test
新),make
会执行编译命令。如果
test
已经是最新的(test.c
未修改),make
会报告'test' is up to date
并退出
那么我们为什么要在clean前加一个.PHONY:clean呢?
大家请看:
在上面操作中,我们多次执行make指令,但是发现,在生成了test可执行文件后,如果test.c文件没有修改,你就不能继续执行make,会有一个报错提示,但是make clean却能随意执行多次,这就是我们.PHONY的作用。
.PHONY的作用。
在 Makefile 中,.PHONY
用于声明一个目标是“伪目标”(Phony Target),即该目标不代表一个实际要生成的文件,而仅仅是一个命令集合。它的主要作用包括:
1. 避免与同名文件冲突
默认情况下,Make 会检查目标是否对应一个实际文件:
如果存在同名文件,并且依赖项没有更新,Make 会认为该目标已经是最新的,从而跳过执行。
使用
.PHONY
可以强制 Make 无条件执行该目标下的命令,即使存在同名文件。
示例:
clean:
rm -rf *.o myprogram
如果没有
.PHONY
:如果当前目录下有一个文件叫
clean
,运行make clean
时,Make 会认为clean
已经是最新的,从而不执行删除操作。
使用
.PHONY
后:.PHONY: clean clean: rm -rf *.o myprogram
无论是否存在
clean
文件,make clean
都会强制执行rm
命令。
2. 提高 Makefile 的可读性
.PHONY
可以明确告诉 Make 和开发者:
该目标不生成任何文件,仅用于执行某些操作(如清理、安装、测试等)。
常见的伪目标包括:
clean
(清理构建文件)all
(默认构建所有目标)install
(安装程序)test
(运行测试)dist
(打包发布)
Makefile的智能之处
当我们炒菜前发现鸡蛋不新鲜了
如果你换了新的鸡蛋:
只需要重新"打鸡蛋"
然后重新"炒菜"
最后"装盘"
不需要重新"切西红柿"
对应编程
当你只修改了utils.cpp:
只需要重新编译utils.o
然后重新链接main.exe
不需要重新编译main.o
假设你有一个hello.cpp
文件:
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!" << endl;
return 0;
}
对应的Makefile可以这样写:
# 这个Makefile用于编译hello.cpp
hello: hello.cpp
g++ hello.cpp -o hello
解释:
hello
:我们要生成的可执行文件hello.cpp
:需要这个文件来生成hellog++...
:具体的编译命令
使用方法:
把这段代码保存为
Makefile
(注意大小写)在终端输入
make
就会生成一个名为
hello
的可执行程序
多个文件的情况
假设现在有3个文件:
main.cpp # 主程序
tools.cpp # 工具函数
tools.h # 工具函数声明
Makefile可以这样写:
myapp: main.o tools.o
g++ main.o tools.o -o myapp
main.o: main.cpp tools.h
g++ -c main.cpp -o main.o
tools.o: tools.cpp tools.h
g++ -c tools.cpp -o tools.o
clean:
rm -f *.o myapp
这个Makefile可以做三件事:
make
:编译整个程序make clean
:删除生成的临时文件自动判断哪些文件需要重新编译
介绍了这么久的Makefile使用场景,下面让我们来简单介绍一个Makefile文件的简单书写吧!
Makefile基本规则详解
1. 变量定义
可以定义变量让Makefile更易读:
# 定义编译器
COMPILER = g++
# 定义编译选项
FLAGS = -Wall -O2
myapp: main.o tools.o
$(COMPILER) $(FLAGS) main.o tools.o -o myapp
使用$(变量名)
来引用变量
2. 自动变量
Makefile有一些特殊变量:
$@
:代表目标名$<
:代表第一个依赖文件$^
:代表所有依赖文件
使用示例:
myapp: main.o tools.o
g++ $^ -o $@
等同于:
myapp: main.o tools.o
g++ main.o tools.o -o myapp
3. 模式规则
当有很多类似规则时,可以用%
简化:
%.o: %.cpp
g++ -c $< -o $@
这条规则的意思是:
"任何.o文件都从同名的.cpp文件生成"
Makefile的运行原理
1.makefile文件,会被make从上到下开始扫描,第一个目标名,是缺省要形成的。如果我们想执行其他组的依赖关系和依赖方法,就使用make + name(对应方法名字)
2.make makfile在执行gcc命令的时候,如果发生了语法错误,就会终止推导过程
3.make解释makefile的时候,是会自动推导的。一直推导,推导过程,不执行依赖方法(类似递归或者入栈出栈的逻辑)。直到推导到有依赖文件存在,然后在逆向的执行所有的依赖方法
完整的Makefile模板
这里提供一个新手友好的模板:
# 1. 定义编译器
CC = g++
# 2. 定义编译选项
CFLAGS = -Wall -O2
# 3. 定义最终程序名
TARGET = myapp
# 4. 定义所有需要的.o文件
OBJS = main.o tools.o
# 5. 最终目标
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) $^ -o $@
# 6. 生成.o文件的通用规则
%.o: %.cpp
$(CC) $(CFLAGS) -c $< -o $@
# 7. 清理功能
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET)
如何使用Makefile
编译程序:在命令行终端输入make
清理生成的文件:输入make clean
如果修改了某个.cpp文件,再次运行
make
时,只会重新编译修改过的文件
总结
Makefile的核心就是:
定义目标(要生成什么)
列出依赖(需要什么文件)
给出命令(如何生成)
记住这个模式,你就能写出基本的Makefile了!刚开始可能会觉得有点难,但写几次就会变得很简单。