Linux系统下的Makefile解析

发布于:2025-07-20 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

什么是Makefile?

为什么需要Makefile?

依赖关系与依赖方法:

.PHONY的作用。

示例:

Makefile的智能之处

对应编程

多个文件的情况

Makefile基本规则详解

1. 变量定义

2. 自动变量

3. 模式规则

Makefile的运行原理

完整的Makefile模板

如何使用Makefile

总结


什么是Makefile?

Makefile是软件开发中的自动化构建工具,它定义了如何将源代码转换为可执行程序的完整过程。就像厨师需要食谱来指导烹饪过程一样,开发者需要Makefile来指导构建过程。Makefile的核心是依赖关系构建规则的集合。


在Linux/Unix开发环境中,Makefile通常与make命令配合使用,能够:

  • 自动化编译过程

  • 处理多文件项目的复杂依赖

  • 只重新编译修改过的文件

  • 提供统一的构建接口

为什么需要Makefile?


考虑以下开发场景:

  1. 项目包含数十个源文件,手动编译效率低下

  2. 源文件之间存在复杂依赖关系

  3. 需要为不同平台定制编译选项

  4. 构建过程包含预处理、编译、链接等多个步骤

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的智能之处

当我们炒菜前发现鸡蛋不新鲜了

如果你换了新的鸡蛋:

  1. 只需要重新"打鸡蛋"

  2. 然后重新"炒菜"

  3. 最后"装盘"

  4. 不需要重新"切西红柿"

对应编程

当你只修改了utils.cpp:

  1. 只需要重新编译utils.o

  2. 然后重新链接main.exe

  3. 不需要重新编译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:需要这个文件来生成hello

  • g++...:具体的编译命令

使用方法:

  1. 把这段代码保存为Makefile(注意大小写)

  2. 在终端输入make

  3. 就会生成一个名为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可以做三件事:

  1. make:编译整个程序

  2. make clean:删除生成的临时文件

  3. 自动判断哪些文件需要重新编译

介绍了这么久的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

  1. 编译程序:在命令行终端输入make

  2. 清理生成的文件:输入make clean

  3. 如果修改了某个.cpp文件,再次运行make时,只会重新编译修改过的文件

总结

Makefile的核心就是:

  1. 定义目标(要生成什么)

  2. 列出依赖(需要什么文件)

  3. 给出命令(如何生成)

记住这个模式,你就能写出基本的Makefile了!刚开始可能会觉得有点难,但写几次就会变得很简单。


网站公告

今日签到

点亮在社区的每一天
去签到