自动化构建工具:make 与 Makefile

自动化构建工具:make 与 Makefile

1. 什么是 make / Makefile?

make 是一个自动化构建工具,它根据一个叫做 Makefile 的文件中的规则,自动编译和链接程序。更本质一点说,make是一个命令,makefile是一个文件
Makefile 定义了文件之间的依赖关系以及如何生成目标文件。

注意:Makefile 文件名首字母可以大写也可以小写,但通常使用 Makefilemakefile


2. Makefile 的基本结构

依赖关系与依赖方法

一个典型的规则如下:

makefile 复制代码
target: dependencies
    recipe
  • 第一行target 是要生成的目标文件,dependencies 是生成该目标所依赖的文件。
  • 第二行recipe 是生成目标的命令(必须以 Tab 键开头)。

示例:

makefile 复制代码
myproc:myproc.c
    gcc -o myproc myproc.c
  • myproc 是目标文件,依赖 myproc.c
  • 依赖方法:gcc -o myproc myproc.c

make 的执行规则

  • make 命令默认扫描当前目录下的 Makefile,并执行 第一个 目标。
  • 如果依赖文件的时间戳比目标文件新(即内容被修改过),则重新生成目标;否则跳过。
  • 这样设计可以避免重复编译未修改的代码,提高构建效率。

3. 如何判断文件是否被修改?

之前说过,文件=内容+属性

文件的三个时间戳(stat 命令)

在 Linux 中,每个文件都有三个时间戳:

时间戳 含义
Access 最近一次访问时间(读文件)
Modify 最近一次内容修改时间
Change 最近一次属性修改时间

注意:

  • 修改文件内容时,Modify 和 Change 都会更新(因为文件大小等属性变了)。
  • 单独修改属性(如 chmod)只会更新 Change。
  • Access 时间不会每次都更新(为了减少磁盘 I/O),通常访问多次后才刷新一次。

make 的判断依据

make 只根据 Modify 时间来判断文件内容是否发生了更改

如果依赖文件的 Modify 时间晚于目标文件,则重新生成。

使用 touch 修改时间

touch 命令除了可以创建空文件,还可以将所有时间戳更新为当前时间:

bash 复制代码
touch filename   # 将 filename 的 Access、Modify、Change 都改为当前时间

利用这个特性,可以强制让 make 认为文件被修改过,从而总是执行某个目标。


touch可以统一更改时间到当前时刻

为什么讲这些呢?

因为这些是关键字PHONY的原理

PHONY可以使程序可以总是被执行,下图中的clean就是如此,原理就是通过touch修改时间

所以我们使用make clean的时候可以总是被执行

如果改成下图这样,make也可以总是被执行,但为了节省编译时间,通常不会这样做

4. 伪目标(.PHONY)

为什么需要伪目标?

有时候我们想执行一些不生成具体文件的操作(例如 clean 删除中间文件)。但 clean 本身不是一个真实的文件,如果恰好当前目录下有一个名为 clean 的文件,make clean 就会认为目标已经存在且无依赖,从而跳过执行。

使用 .PHONY 声明伪目标

makefile 复制代码
.PHONY: clean
clean:
    rm -f *.o hello
  • .PHONY 告诉 make:clean 不是一个真实文件,无论是否存在,都执行它的命令。
  • 伪目标的原理本质上是 make 强制把它的依赖时间视为"总是比目标新",从而总是执行。

能否让普通目标也总是执行?

可以,但不推荐。例如:

makefile 复制代码
hello: hello.c
    gcc hello.c -o hello
    touch hello   # 更新 hello 的时间戳,这样下次 make 时会认为 hello 比依赖新,从而不编译

这样会导致每次 make 都重新编译,失去了增量编译的优势。因此,一般只对 clean 等辅助目标使用 .PHONY


5. Makefile 的推导规则(自动推导)

拆分写法(不常用,仅用于理解)

上述的命令可以拆分成下图(一般不这样写,为了讲解原理而拆分)

makefile会从上向下扫描,没有就入栈,有就出栈执行

在实际开发中,我们一般这样写

其中(BIN) 和 (SRC)替换成(SRC) 替换成(SRC)替换成@和^

  • $@ 表示目标文件。
  • $^ 表示所有依赖文件。

如果不想回显命令,可以在前边加上@

如果想看起来直观一点,可以在命令后输出一行,指定内容

make 会从上向下扫描,发现需要 hello.o,则先去执行生成 hello.o 的规则,再返回执行 hello 的规则。

简化写法(常用)

实际开发中,我们通常会使用变量和自动化变量来简化:

makefile 复制代码
BIN = hello
SRC = hello.c

$(BIN): $(SRC)
    gcc -o $@ $^

隐藏命令回显

在命令前加 @ 可以阻止 make 输出该命令本身:

makefile 复制代码
$(BIN): $(SRC)
    @gcc $^ -o $@
    @echo "编译完成"

处理多个源文件

  • %.o: %.c 是一个模式规则,表示所有 .o 文件都依赖对应的 .c 文件。
  • $< 表示第一个依赖文件(即对应的 .c 文件)。

6. Makefile 的终极形态(通用模板)

说明:

  • wildcard 函数:展开当前目录下所有 .c 文件。
  • SRC:.c=.o 是变量替换语法,将所有 .c 后缀替换为 .o
  • 这个模板可以自动适应任意数量的 .c 文件,无需手动列出。

makefile 复制代码
SRC=$(shell ls *.c)
SRC=$(wildcard *.c) #两种方式都可以 显示当前目录下的所有.c文件
OBJ=$(SRC:.c=.o) #把所有SRC内部的.c文件替换成.o文件

总结

概念 说明
依赖关系 目标文件依赖哪些源文件或中间文件
依赖方法 生成目标的具体命令,前面必须有一个 Tab
时间戳 make 根据文件的 Modify 时间判断是否需要重新编译
伪目标 .PHONY 声明,总是执行其命令(如 clean
自动化变量 $@(目标)、$^(所有依赖)、$<(第一个依赖)
模式规则 %.o: %.c 表示所有 .o 依赖同名 .c
函数 wildcardshell 等用于动态获取文件列表

掌握这些内容,你就可以编写灵活、高效的 Makefile,实现项目的自动化构建。

新编译 |

| 伪目标 | 用 .PHONY 声明,总是执行其命令(如 clean) |

| 自动化变量 | $@(目标)、$^(所有依赖)、$<(第一个依赖) |

| 模式规则 | %.o: %.c 表示所有 .o 依赖同名 .c |

| 函数 | wildcardshell 等用于动态获取文件列表 |

掌握这些内容,你就可以编写灵活、高效的 Makefile,实现项目的自动化构建。

相关推荐
Xiu Yan2 小时前
Java 转 C++ 系列:STL常用函数
java·开发语言·c++·stl·visual studio
.ZGR.2 小时前
【全栈实战】搭建属于你的AI图像生成平台:从Java Swing 到 Web 应用
java·人工智能·node.js
:1212 小时前
java面试基础
java·开发语言
小樱花的樱花2 小时前
Linux Shell命令入门
linux·服务器·开发语言
艾莉丝努力练剑2 小时前
【Linux网络】计算机网络入门:从背景到协议,理解网络通信基础
linux·运维·服务器·c++·学习·计算机网络
_Evan_Yao2 小时前
软件工程就是一场“抽象”游戏:从 abstract 关键字到架构设计的认知跃迁
java·后端·游戏·状态模式·软件工程
艾莉丝努力练剑2 小时前
【Linux线程】Linux系统多线程(十):线程安全和重入、死锁相关话题
java·linux·运维·服务器·c++·学习·安全
运维老郭2 小时前
Nginx vs Envoy:高并发负载均衡实战指南(含踩坑记录)
linux·运维
小娄~~2 小时前
特殊进程-
linux·运维·服务器