Linux下的项目自动化构建-make\makefile详解

文章目录

1. 背景知识

  • 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
  • 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
  • -makefile带来的好处就是------"自动化编译",一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
  • make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
  • make是一条命令,makefile是一个文件 ,两个搭配使用,完成项目自动化构建。

    假如一个项目一共有1w个源文件,难道一个一个gcc编译吗?这显然是不可能的,这里就需要我们项目的自动化构建

2.基本使用

2.1make/makefile的初识

  1. 创建一个在当前目录下的Makefile或者makefile 首字母大小写都行,然后打开Makefile
  2. 在内部写下以下内容,保存退出
  3. 然后 输入make 发现自动执行了gcc命令

    这就是最简单的Makefile

2.2 make命令和Makefile的依赖关系和依赖方法

什么是依赖关系、依赖方法?

比如月底,你没钱了,你和你爸是父子关系,所以你找他打电话要钱,这层关系叫依赖关系 ,而打电话要钱的行为就叫依赖方法 。只有依赖关系依赖方法同时存在且合理,你才能达到你要钱的目的。

依赖关系

  • make 命令依赖 Makefilemake 命令是一个解释执行工具,它需要读取 Makefile 文件中的规则来确定如何编译、链接项目中的文件。如果没有 Makefilemake 命令无法执行自动化构建操作。
  • Makefile 定义依赖规则:Makefile 中通过一系列规则来定义文件之间的依赖关系(如目标文件依赖于源文件、头文件等),以及对应的构建命令。make 命令根据这些依赖规则来判断哪些文件需要重新编译、链接,从而实现增量构建,提高开发效率。

依赖方法(语法要求)

格式:目标文件: 依赖文件列表

复制代码
test.exe: test.c
	gcc -o test.exe test.c   #单行缩进使用Table键

这里声明test.exe依赖于test.c,当test.c或有更新时,make 会执行后面的编译命令重新生成test.exe

同时依赖关系依赖方法可以多重定义书写

这里make会形成推导栈,通过依赖方式的入栈,直到找到突破口(这里的突破口就是test.c文件的存在),从而进行栈返回,类似于函数递归的方式一步步生成可执行文件。

以下为流程图:

2.3 项目清理(伪目标依赖)

工程是需要被清理的 这里就提到一个新的概念伪目标依赖.

格式:.:PHONY :核心伪目标

这样项目就被清理了

细节解析:

  1. 依赖关系必须存在,但是依赖列表可以为空。
  2. 依赖方法(Makefile文件内的内容)可以是任何shell命令(可以gcc也可以rm)。
  3. clean目标,只是利用make的自动推导能力,让他执行了rm命令,在构建工程视角,看起来就是清理项目。清理项目,本质就是删除不需要的临时文件。
  4. make命令,后面可以跟"目标名",后面跟谁,就解析谁的依赖关系和依赖方法,同时make默认只会推导一条完整的推导链路!当make后面没有跟目标名字的时候默认推导第一个依赖关系对应的推导链。
    默认推导:

    当修改顺序:

    此时:
PHONY解析(伪目标)

.PHONY是用来修饰目标文件是一个伪目标,本质就是总是被执行

什么叫总是被执行

当我每次make 生成可执行程序时候,发现只有第一次可以成功。

只有当源文件发生改变以后我们才能再次执行。

但是make clean确能一直执行

当我们为我们的命令加上.PHONY

此时就能总是执行


为什么我们可用执行程序往往不.PHONY修饰,gcc为什么无法二次编译老代码???

源代码如果在没有修改的情况下可以一直编译,这不是一个极度浪费资源的行为吗?假如一共有一万个源文件,哪怕你只改了一个源文件,也要全部重新编译。所以make默认只会重新编译你修改过的文件不会重复编译!

那么gcc如何做到的?

我们可以通过stat命令查看文件ACM时间信息

Access:访问内容时间。

Modify:文件内容修改时间。

Change:文件属性修改时间。

Birth:文件创建时间。

判断文件是否要被重新编译,靠的就是.exe文件和源文件的Mod时间的对比!当源文件比可执行文件旧,就无法进行重新编译,当你修改源文件内容,Mod时间就会更新,这样你就可以重新编译了。

那么我们touch直接修改文件时间是不是又可以重新编译了?

答案是:是的。

当然文件时间还有更多细节部分。

文件时间细节(加餐)

当我们修改文件内容的时候 为什么文件属性时间也发生了改变?

修改文件内容,会影响文件大小,同时Mod时间也会改变,但是文件内容大小和Mod时间本身也是文件属性,所以也会修改Change时间。


assess时间修改细节

我们多次使用cat命令访问文件


我们发现两次access时间都没有改变,这个是为什么呢?

文件内容发生更改是会从内存刷新到磁盘上的,文件的属性也是数据,每次更改也会刷新到磁盘上面。

我们修改文件和查看文件属性内容的频率是大大不一样的,明显查看文件比重更高。

而查看文件就会->更新时间->修改文件属性->刷新磁盘!

而每次查看文件就要刷新磁盘,就会导致增加访问磁盘的次数,而这种外设本身就效率低下,最终就会导致OS整体效率低下。

所以,就规定访问文件内容,特定次数后,才会刷新一次!

2.4 Makefile编辑语法细节

@(禁止命令回显)

我们发现此时命令被回显出来

我们可以利用@符号禁止命令回显

效果:

变量替换

我们同样可以这么写

类似于宏一样定义变量,这样可以方便修改,$表示为内容提取符号。

依赖方法同样可以替换成符号:

这里@和^都是定义好的内置变量 $@表示对应依赖关系中的目标文件 $^表示对应依赖关系中的依赖列表

多文件替换

假如一共有199个源文件我们不可能写一百九十九个依赖关系和依赖方法吧。

我们先创建199个文件 这里的{1..199}表示1~199

test.c改名为main.c作为主函数文件

我们把先前写得备份起来 #就是shell语句中的注释

做法一:采用 shell命令行方式,获取当前所 有.c文件名

格式:$(shell 命令)

测试:

做法二:使用wildcard(通配符),获取当前所有.c文件名

这里wildcard XXX表示获取当前所有XXX文件名字

测试:


然后将 SRC的所有同名 .c 替换成为.o 形成目标文件列表 语法如下:

测试:

结果:


接下来 书写他们的依赖关系和方式

前置知识:

当我们gcc -c xxx.c 会自动生成对应的.o文件

这里的%.c\%.o表示文件里所有的XXX.c和XXX.o文件 %是一种通配符。

同时$< 指代规则中 "第一个依赖文件" 即 .c 文件 然后生成对应的 .o 文件 相当于将其展开成200行gcc -c XXX代码:

展示:


有了创建 肯定还要有清理

展示:

替换进阶内容(命令包装)

如果我们不光想编译C语言 而是别的语言呢? 所以 我们这里的Makefile还可以再次优化

至此 意味着我们可以把该Makefile放入任何目录 我们只需要改变"别名"就可以编译清除任何项目程序了。

2.5 \< 和 ^ 的区别(加餐)

Makefile 中,$^$< 都是 自动变量 (预定义变量),核心区别在于 指代的依赖文件范围不同,适用场景完全不同,以下是清晰对比和实战示例:

一、核心区别总结表

自动变量 核心含义 关键特点 适用场景
$< 指代 第一个依赖文件 只取依赖列表的「第一个」,忽略后续所有依赖 1. 隐式规则(如 .c→.o 编译); 2. 核心依赖明确的场景(如链接时只需要第一个 .o 触发)
$^ 指代 所有依赖文件(自动去重) 包含依赖列表中所有文件,重复依赖会合并 1. 链接多个 .o 文件生成可执行程序; 2. 需用到所有依赖的场景(如打包、多文件编译)

二、示例(基于 test.exe 多文件场景)

假设项目有 2 个源文件(test.cutils.c)和 1 个头文件(common.h),最终生成 test.exe,通过示例看两者区别:

项目文件结构
复制代码
test.c    # 主程序源码
utils.c   # 工具函数源码
common.h  # 公共头文件(两个 .c 文件都依赖)
Makefile  # 编译规则
Makefile 规则示例
复制代码
# 定义变量:所有源文件、目标文件、可执行文件
SRC = test.c utils.c
OBJ = $(SRC:.c=.o)  # 推导目标文件:test.o、utils.o
BIN = test.exe

# 规则1:生成可执行文件(依赖所有 .o 文件 + 头文件)
$(BIN): $(OBJ) common.h
    gcc $^ -o $@  # 这里必须用 $^,需链接所有 .o 文件
    # 展开后:gcc test.o utils.o common.h -o test.exe(头文件不影响编译,仅用于判断是否重新编译)

# 规则2:生成目标文件(每个 .o 依赖对应的 .c 文件 + 头文件)
%.o: %.c common.h
    gcc -c $< -o $@  # 这里用 $<,仅需编译第一个依赖(.c 文件)
    # 对 test.o 展开:gcc -c test.c -o test.o
    # 对 utils.o 展开:gcc -c utils.c -o utils.o

求三连!!

相关推荐
keep__go2 小时前
zookeeper单机版安装
大数据·运维·zookeeper
chde2Wang2 小时前
Linux中bash: ls: 未找到命令… 相似命令是: ‘lz‘
linux·运维·bug·bash
楼田莉子3 小时前
Linux学习:进程的控制
linux·运维·服务器·c语言·后端·学习
JiMoKuangXiangQu3 小时前
Linux:文件 mmap 读写流程简析
linux·内存管理·file mmap
捷智算云服务3 小时前
H100服务器维修“病历卡”:五大常见故障现象与根源分析
运维·服务器
wzlsunice883 小时前
用vir-manager创建kvm虚拟机(创建网桥和配置网络等)
运维·网络
北京耐用通信4 小时前
冶金车间“迷雾”重重?耐达讯自动化Profibus转光纤为HMI点亮“透视眼”!
人工智能·物联网·网络协议·网络安全·自动化
晓风伴月4 小时前
AI: n8n工作流自动化
自动化·n8n
北京耐用通信4 小时前
耐达讯自动化Profibus光纤模块:智能仪表的“生命线”,极端环境通信无忧!
人工智能·物联网·网络协议·自动化·信息与通信