Makefile 完全指南:从入门到工程化,自动化构建不再难

一. Makefile 核心认知:什么是 Makefile?

1.1 背景介绍

  • 能否编写 Makefile,从侧面反映了一个人是否具备完成大型工程的能力。
  • 在一个工程中,源文件数量众多,并按类型、功能、模块分别存放在不同目录中。Makefile 定义了一系列规则,用于指定哪些文件需要先编译、哪些需要后编译、哪些需要重新编译,甚至支持更复杂的操作。
  • Makefile 的核心优势在于自动化编译 。一旦编写完成,只需执行 make 命令,整个工程即可自动完成编译,极大地提高了软件开发效率。
  • make 是一个命令行工具,用于解释 Makefile 中的指令。大多数 IDE 都集成了该工具,例如 Delphi 的 make、Visual C++ 的 nmake、Linux 下的 GNU make。因此,Makefile 已成为工业界广泛采用的编译构建方法。
  • make 是一条命令,Makefile 是一个文件,二者配合使用,共同完成项目的自动化构建。

1.2 核心作用

  • 定义依赖关系:明确源文件之间的依赖关系,例如可执行文件依赖目标文件,目标文件依赖源文件。
  • 指定编译规则 :规定编译流程,例如如何将 .c 文件编译为 .o 文件,如何链接生成可执行文件。
  • 支持增量编译:只重新编译被修改过的文件,避免不必要的重复工作,提升编译效率。
  • 提供清理功能:支持一键删除编译产物,方便项目重新构建。

1.3 核心概念

  • make :用于解释 Makefile 规则的命令工具,默认会在当前目录下查找名为 Makefilemakefile 的文件。
  • 目标(target):指要生成的文件或要执行的操作,例如生成可执行文件、执行清理操作。
  • 依赖(prerequisites) :生成目标所需要的文件或前置条件,例如生成 myproc 依赖于 myproc.c
  • 命令(command) :生成目标的具体操作,例如 gcc -o myproc myproc.c,命令行必须以 Tab 键开头。

二. 入门案例:最简单的 Makefile

假设项目只有一个源文件hello.c,要生成可执行文件hello,并支持清理编译产物。

2.1 源文件hello.c

bash 复制代码
#include<stdio.h>
 
int main()
{
	printf("hello Makefile!");
	return 0;
}

2.2 基础 Makefile

bash 复制代码
# 目标:依赖
code: code.c
	# 命令(必须Tab开头):将code.c编译为可执行文件code
	gcc -o code code.c

# 伪目标:清理编译产物
.PHONY: clean
clean:
	# -f表示强制删除,忽略不存在的文件
	rm -f code

2.3 实际操作演示

bash 复制代码
[Scy@VM-0-3-centos lesson10]$ make
gcc -o hello hello.c
[Scy@VM-0-3-centos lesson10]$ ll
total 912
-rwxrwxr-x 1 Scy Scy   8360 Nov 20 13:45 hello
-rw-rw-r-- 1 Scy Scy     81 Nov 20 13:44 hello.c
-rw-rw-r-- 1 Scy Scy     76 Nov 20 13:00 hello_copy.c
-rwxrwxr-x 1 Scy Scy   8360 Nov 20 09:23 hello_dynamic
-rwxrwxr-x 1 Scy Scy 861216 Nov 20 09:22 hello_static
-rw-rw-r-- 1 Scy Scy     69 Nov 20 13:44 makefile
-rwxrwxr-x 1 Scy Scy   8424 Nov 19 23:39 soft
-rw-rw-r-- 1 Scy Scy    250 Nov 19 22:27 soft.c
-rw-rw-r-- 1 Scy Scy     90 Nov 19 23:11 soft.cpp
-rwxrwxr-x 1 Scy Scy   8968 Nov 19 23:12 softpp
[Scy@VM-0-3-centos lesson10]$ ./hello
hello Makefile!
[Scy@VM-0-3-centos lesson10]$ make clean
rm -f hello
[Scy@VM-0-3-centos lesson10]$ ll
total 900
-rw-rw-r-- 1 Scy Scy     81 Nov 20 13:44 hello.c
-rw-rw-r-- 1 Scy Scy     76 Nov 20 13:00 hello_copy.c
-rwxrwxr-x 1 Scy Scy   8360 Nov 20 09:23 hello_dynamic
-rwxrwxr-x 1 Scy Scy 861216 Nov 20 09:22 hello_static
-rw-rw-r-- 1 Scy Scy     69 Nov 20 13:44 makefile
-rwxrwxr-x 1 Scy Scy   8424 Nov 19 23:39 soft
-rw-rw-r-- 1 Scy Scy    250 Nov 19 22:27 soft.c
-rw-rw-r-- 1 Scy Scy     90 Nov 19 23:11 soft.cpp
-rwxrwxr-x 1 Scy Scy   8968 Nov 19 23:12 softpp

核心用法

  • 编译项目: 在 Makefile 所在目录执行make,自动查找第一个目标(code),检查依赖是否更新,执行编译命令;
  • 清理项目: 执行make clean,执行clean目标下的命令,删除可执行文件;
  • 增量编译: 修改code.c后再次执行 make,只会重新编译修改后的文件,而非全部重编。

三. Makefile 语法规则:深入理解依赖与命令

3.1 基本语法格式

bash 复制代码
目标(target): 依赖(prerequisites)
	命令1
	命令2
	...
  • 目标可以是可执行文件、目标文件(.o)、伪目标(如clean);
  • 依赖可以是源文件、目标文件、其他目标;
  • 命令必须以Tab 键 开头(不能用空格,否则make会报错);
  • 注释以#开头,单行有效。

3.2 依赖关系与推导

参考图示 :(Makefile 会自动推导目标文件的依赖)

但是我们一般是用不到这么多步的,最佳实践如下:

bash 复制代码
# 最终目标:可执行文件code.exe,依赖code.o
code.exe: code.o
	gcc code.o -o code.exe

# 目标文件code.o,依赖code.c
code.o: code.c
	gcc -c code.c -o code.o

# 伪目标clean
.PHONY: clean
clean:
	rm -f code.exe code.o

3.3 伪目标(.PHONY)

  • 伪目标不是实际文件 ,而是一个 "命令标签"(如clean);
  • 作用 :避免项目中存在与伪目标同名的文件,导致make误判为 "目标已存在,无需执行";
  • 语法/.PHONY: 伪目标名,例如/.PHONY: clean,确保make clean总是执行命令。

📌 结论:
.PHONY:让make忽略源文件和可执行目标文件的M时间对比

图示理解 :(什么叫总是被执行?)

四. 进阶用法:变量与函数(工程化必备)

当项目源文件增多时,手动写每个文件的依赖和命令会很繁琐。Makefile 支持变量和函数,可简化配置、提高复用性。

4.1 变量定义与使用

变量用于存储重复出现的内容(如编译器、编译参数、文件列表),语法: 变量名=值 ,使用时 $(变量名)

示例

bash 复制代码
# 定义变量:编译器、可执行文件名、目标文件列表、清理命令
CC = gcc          # 编译器
BIN = myproc      # 可执行文件名
OBJ = myproc.o    # 目标文件列表
RM = rm -f        # 清理命令
CFLAGS = -Wall -g # 编译参数(-Wall显示所有警告,-g生成调试信息)

# 最终目标:依赖OBJ变量
$(BIN): $(OBJ)
	$(CC) $(CFLAGS) -o $(BIN) $(OBJ)

# 目标文件依赖源文件(可省略,make自动推导)
myproc.o: myproc.c

# 清理伪目标
.PHONY: clean
clean:
	$(RM) $(BIN) $(OBJ)

4.2 常用函数(简化文件列表)

Makefile 提供内置函数,可自动获取文件列表,无需手动罗列。
(1)wildcard:获取指定模式的文件

bash 复制代码
# 获取当前目录下所有.c文件,存入SRC变量
SRC = $(wildcard *.c)

(2)替换文件后缀

bash 复制代码
# 将SRC中的.c文件替换为.o文件,存入OBJ变量
Obj=$(Src:.c=.o)

4.3 自动变量(简化命令)

Makefile 提供自动变量,替代命令中重复出现的目标和依赖,简化书写:

自动变量 含义 示例
$@ 当前目标文件名 $(CC) -o $@ $^
$^ 所有不重复的依赖文件 $(CC) -o $@ $^
$< 第一个依赖文件名 $(CC) -c $< -o $@

4.4 工程化示例(多源文件适配,简化 Makefile/makefile)

假设项目有多个源文件,Makefile 可自动适配,简化后的 Makefile 如下所示:

bash 复制代码
Bin=code.exe         # 定义变量     
#Src=$(shell ls *.c) # 做法1 -- 采用shell命令行方式,获取当前所有.c文件名    
Src=$(wildcard *.c)  # 做法2 -- 使用 wildcard 函数,获取当前所有.c文件名    
Obj=$(Src:.c=.o)     # 将Src的所有同名 .c 替换成 .o 形成目标文件列表    
Echo=echo               
cc=gcc     
Rm=rm -f    
Flags=-c -Wall       # 编译选项    
LD_Flags=-o          # 链接选项    
    
$(Bin):$(Obj)    
    @$(Echo) "我要开始链接了...$(Obj) -> $(Bin)"    # $@: 代表目标文件名    $^: 代表依赖文件列表    
    @$(cc) $(LD_Flags) $@ $^                            
%.o:%.c                                             # %.c: 展开当前目录下的所有.c  %.o: 同时展开同名.o    
    @$(Echo) "我要开始编译了...$< -> $@"            # @: 不回显命令    
    @$(cc) $(Flags) $<                              # %<: 对展开的依赖.c文件,一个个的交给gcc    
.PHONY:clean    
clean:    
    $(Rm) $(Obj) $(Bin)                             # $(RM): 替换,用变量内容替换它                                            
                           
.PHONY:debug                                                 
debug:                                                                           
    @$(Echo) "Bin: $(Bin)"             
    @$(Echo) "Obj: $(Obj)"                               
    @$(Echo) "Src: $(Src)"               
~
相关推荐
DeeplyMind1 小时前
第11章 容器运行参数详解
运维·docker·容器
成震19712 小时前
UBUNTU 安装虚拟机
linux·运维·ubuntu
最贪吃的虎2 小时前
windows上如何可视化访问并远程操作linux系统上运行的浏览器或者linux可视化桌面
java·linux·运维·windows·分布式·后端·架构
Turboex邮件分享2 小时前
邮件队列堵塞的深度排查与紧急清空/重定向实战
运维·网络
mzhan0172 小时前
Linux: socket创建之后 interface down 然后再up起来
linux·运维
heimeiyingwang2 小时前
向量数据库VS关系数据库VS非关系数据库
运维·人工智能·重构·架构·机器人
之歆2 小时前
Linux 软件包管理与编译安装
linux·运维·服务器
Linux运维技术栈2 小时前
实战运维|CentOS7 Nexus3.21.1 迁移至 Rocky Linux9.5 + 升级至3.68.1
运维·nexus3
麦德泽特2 小时前
OpenWrt在机器人中的高级网络应用:AP+STA模式、中继与防火墙配置实战
运维·网络·机器人