掌握Linux项目自动化构建:从零入门make与Makefile

文章目录
- 前言:
- 一、初识自动化构建工具
- 1.1 什么是make/Makefile?
- 1.2 快速体验
- 二、深入理解核心机制
- 2.1 依赖关系与依赖方法
- 2.2 伪目标的妙用
- 2.3 具体语法
- a.makefile的基本雏形
- b.makefile推导原则!
- 三、更加具有通用型的makefile
- 1. 变量定义部分
- 2. 编译规则部分
- 3. 模式规则(通配规则)
- 4. 伪目标(`.PHONY`)
- 5. 完整执行流程示例
- 6. 新手常见问题
- 总结
- 四、高手必备的实用技巧
- 1.调试 Makefile
- 2. 常见问题与解决方案
- Q1:修改头文件后 `make` 不重新编译?
- Q2:如何指定其他名称的 Makefile?
- Q3:如何实现跨平台编译?
前言:
“不会写Makefile的程序员,就像不会用筷子的美食家——永远尝不到工程化开发的精髓。”
在Windows环境下我们习惯使用Visual Studio等IDE的一键编译,但在Linux开发环境中,掌握Makefile就像获得了一把打开高效开发之门的钥匙。它能让你:
- 实现真正的自动化编译 - 一个命令完成整个项目的构建
- 提升编译效率 - 只重新编译修改过的文件
- 管理复杂项目 - 轻松处理多文件、多目录的依赖关系
- 跨平台移植 - 一套构建规则适应不同开发环境
一、初识自动化构建工具
1.1 什么是make/Makefile?
在Linux开发中,make是一个智能编译命令,而Makefile是它的配置文件。这对组合就像烹饪食谱:
- Makefile是菜谱(记录食材和步骤)
- make是厨师(按菜谱自动执行)
1.2 快速体验
步骤演示:3分钟完成第一个自动化构建
- 创建测试文件
# test.c
#include <stdio.h>
int main() {printf("Hello Makefile!\n");return 0;
}
- 编写Makefile
# 基础版Makefile
mytest: test.cgcc test.c -o mytest.PHONY: clean
clean:rm -f mytest
- 一键编译运行
$ make # 自动编译
$ ./mytest # 运行程序
hello Makefile!
$ make clean # 清理项目
二、深入理解核心机制
2.1 依赖关系与依赖方法
核心思想:依赖关系和依赖方法,形成目标文件。
mytest: test.c # 依赖关系gcc test.c -o mytest # 依赖方法
理解这两个概念是掌握Makefile的关键:
eg:月底了,没钱了,要让爸爸打钱。
| 概念 | 生活案例 | 技术解释 |
|---|---|---|
| 依赖关系 | “我是你儿子” | 目标文件与源文件的关联 |
| 依赖方法 | “打钱” | 生成目标文件的具体命令 |
这两者必须同时存在,事情才能办成!
2.2 伪目标的妙用
.PHONY标记的特殊目标:
.PHONY: clean
clean:rm -f mytest
- 总是执行清理命令
- 避免与同名文件冲突
- 支持
make clean独立执行
2.3 具体语法
a.makefile的基本雏形
mytest: test.cgcc test.c -o mytest.PHONY: clean
clean:rm -f mytest
-
mytest是目标文件,test.c是依赖文件,而有多个依赖文件就是依赖文件列表;
-
mytest:test.c是依赖关系;
-
clean也是目标文件,依赖文件是空的,下面是方法;
make会自定向下扫描makefile文件,默认形成第一个目标文件
如果想指定形成,make targetname
-
.PHONY是伪目标,所依赖的方法:总是被执行的!
1.为什么没有.PHONY修饰的目标文件,第一次可以编译,之后就不可以去编译了?
- 因为要提高效率。
2.它是怎么做到的?
-
首次编译:目标文件(如可执行文件)不存在,Make工具会直接执行编译命令生成该文件。
-
后续编译:Make工具会比较目标文件和其依赖文件(如源文件)的最后修改时间(Modify Time):
-
若依赖文件比目标文件新(例如源文件被修改过),则重新编译。
-
若目标文件较新或两者时间相同,则跳过编译,认为输出已是最新。
-
3.我们要是想再次编译呢?
-
手动更新文件时间戳可触发编译:
touch test.c make
-
makefile的注释我们用#来注释;
-
stat test.c //显示文件test.c的详细属性信息File: ‘test.c’Size: 1024 Blocks: 8 IO Block: 4096 regular fileDevice: 801h/2049d Inode: 1234567 Links: 1Access: (0644/-rw-r--r--) Uid: ( 1000/ your_username) Gid: ( 1000/ your_groupname)Access: 2024-01-01 12:00:00.000000000 +0800Modify: 2024-01-02 13:00:00.000000000 +0800Change: 2024-01-02 13:00:00.000000000 +0800Birth: -文件=内容+属性
- 改变内容Modify,Access time变化,改变属性Change time变化。
如何手动更新时间戳?
- 修改
atime:touch -a test.c # 仅更新 atime - 修改
mtime:touch -m test.c # 仅更新 mtime - 触发
ctime更新:chmod +x test.c # 修改权限(必然更新 ctime)
b.makefile推导原则!
- make会进行依赖关系的推导,直到依赖文件是存在的。推导的过程我们类似于一个 将依赖方法不断入栈,推导完毕,出栈执行方法!
- 典型处理流程:
三、更加具有通用型的makefile
BIN=mytest
#SRC=$(shell ls *.c)
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
CC=gcc
RM=rm -f$(BIN):$(OBJ)@$(CC) $^ -o $@@echo "链接 $^ 成 $@"
%.o:%.c@$(CC) -c $<@echo "编译 ... $< 成 $@".PHONY:clean
clean:@$(RM) $(OBJ) $(BIN).PHONY:test
test:@echo $(BIN)@echo $(SRC)@echo $(OBJ)
下面我会逐行详细解释这个 Makefile 的每一部分.
1. 变量定义部分
BIN=mytest
#SRC=$(shell ls *.c)
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
CC=gcc
RM=rm -f
| 代码 | 解释 |
|---|---|
BIN=mytest | 定义变量 BIN,表示最终生成的可执行文件名(这里是 mytest)。 |
#SRC=$(shell ls *.c) | 注释掉的代码:用 ls 命令获取所有 .c 文件(不推荐,可能有空格问题)。 |
SRC=$(wildcard *.c) | 正确做法:使用 wildcard 函数获取当前目录下所有 .c 文件列表。 |
OBJ=$(SRC:.c=.o) | 将 SRC 中的 .c 替换为 .o,得到目标文件列表(如 main.c → main.o)。 |
CC=gcc | 定义变量 CC,表示使用的编译器(这里是 gcc)。 |
RM=rm -f | 定义变量 RM,表示删除命令(-f 表示强制删除,不提示)。 |
类比:
BIN像是最终产品的名字(比如“汽车”)。SRC是原材料清单(所有.c文件,比如“发动机.c、轮胎.c”)。OBJ是加工后的零件(.o文件,比如“发动机.o、轮胎.o”)。
2. 编译规则部分
$(BIN):$(OBJ)@$(CC) $^ -o $@@echo "链接 $^ 成 $@"
| 代码 | 解释 |
|---|---|
$(BIN):$(OBJ) | 目标文件 $(BIN) 依赖于所有 .o 文件($(OBJ))。 |
@$(CC) $^ -o $@ | $^ 表示所有依赖文件(.o 文件),$@ 表示目标文件($(BIN))。实际执行: gcc main.o utils.o -o mytest。 |
@echo "链接..." | 打印提示信息(@ 表示不显示命令本身,只输出结果)。 |
关键符号:
$^:所有依赖文件的集合(比如main.o utils.o)。$@:当前目标文件名(比如mytest)。
3. 模式规则(通配规则)
%.o:%.c@$(CC) -c $<@echo "编译 ... $< 成 $@"
| 代码 | 解释 |
|---|---|
%.o:%.c | 模式规则:所有 .o 文件依赖于同名的 .c 文件(如 main.o 依赖 main.c)。 |
@$(CC) -c $< | $< 表示第一个依赖文件(这里是 .c 文件)。实际执行: gcc -c main.c(生成 main.o)。 |
@echo "编译..." | 打印编译过程信息。 |
关键符号:
$<:当前依赖的第一个文件(比如main.c)。
4. 伪目标(.PHONY)
.PHONY:clean
clean:@$(RM) $(OBJ) $(BIN).PHONY:test
test:@echo $(BIN)@echo $(SRC)@echo $(OBJ)
| 代码 | 解释 |
|---|---|
.PHONY:clean | 声明 clean 是一个伪目标(不生成实际文件,仅执行命令)。 |
@$(RM) $(OBJ) $(BIN) | 删除所有 .o 文件和可执行文件 $(BIN)(实际执行:rm -f main.o mytest)。 |
.PHONY:test | 声明 test 是伪目标,用于调试变量。 |
@echo $(BIN)... | 打印变量 BIN、SRC、OBJ 的值(检查变量是否正确)。 |
为什么用
.PHONY?
如果目录下恰好有一个名为clean的文件,Make 会认为clean已是最新而不执行命令。加上.PHONY可以强制执行。
5. 完整执行流程示例
假设目录下有 main.c 和 utils.c:
-
首次运行
make:- 根据
%.o:%.c规则,编译所有.c文件生成.o文件:gcc -c main.c -o main.o gcc -c utils.c -o utils.o - 根据
$(BIN):$(OBJ)规则,链接.o文件生成mytest:gcc main.o utils.o -o mytest
- 根据
-
运行
make clean:- 删除所有
.o文件和mytest:rm -f main.o utils.o mytest
- 删除所有
-
运行
make test:- 打印变量值(用于调试):
echo mytest echo main.c utils.c echo main.o utils.o
- 打印变量值(用于调试):
6. 新手常见问题
-
为什么用
wildcard而不用ls?ls *.c可能因文件名含空格或特殊字符出错,wildcard是 Makefile 内置的安全函数。
-
$^和$<的区别?$^:所有依赖文件(用于链接阶段)。$<:第一个依赖文件(用于编译单个.c文件时)。
-
@的作用?- 禁止命令回显(Make 默认会打印执行的命令,
@让终端只显示命令的输出)。
- 禁止命令回显(Make 默认会打印执行的命令,
总结
- 变量:定义文件名、工具命令等(
BIN,SRC,CC)。 - 规则:指定目标和依赖关系(
目标:依赖)。 - 自动变量:
$@(目标)、$^(所有依赖)、$<(第一个依赖)。 - 伪目标:
.PHONY声明非文件目标(如clean)。
通过这个 Makefile,你可以:
- 编译所有
.c文件生成可执行文件mytest。 - 清理生成的文件(
make clean)。 - 调试变量值(
make test)。
四、高手必备的实用技巧
1.调试 Makefile
$ make -n # 显示将要执行的命令
$ make -d # 显示详细调试信息
- 作用:Makefile 默认会隐藏执行的命令(只显示结果),可以通过以下方式调试:
make -n:仅打印命令但不执行(模拟运行)。make --debug:显示详细的执行过程(如依赖检查、规则匹配)。
2. 常见问题与解决方案
Q1:修改头文件后 make 不重新编译?
main.o: main.c header.h # 显式声明头文件依赖$(CC) -c $< -o $@
- 问题原因:
Makefile 默认只检查.c文件的修改时间,如果header.h被修改但未声明依赖,不会触发重新编译。 - 解决方案:
在目标规则中显式列出所有依赖的头文件(如上例),或通过gcc -MM自动生成依赖关系(推荐)。
Q2:如何指定其他名称的 Makefile?
make -f MyMakefile # 使用自定义文件名(如 MyMakefile)
- 适用场景:
项目中有多个构建配置文件(如Makefile、MyMakefile),需指定其中一个执行。
Q3:如何实现跨平台编译?
ifeq ($(OS),Windows_NT) # 判断是否为 WindowsRM = del /Q # Windows 删除命令
elseRM = rm -f # Linux/macOS 删除命令
endif
- 作用:
根据操作系统动态切换命令,避免平台兼容性问题(如rm在 Windows 中不可用)。 - 扩展:还可用于设置不同的编译器、路径分隔符等。
📌 小贴士:优秀的Makefile就像项目说明书,能让您的代码更易于维护和协作!
希望这篇指南能帮助您开启自动化构建之旅!如有疑问,欢迎在评论区交流讨论~

相关文章:
掌握Linux项目自动化构建:从零入门make与Makefile
文章目录 前言: 一、初识自动化构建工具1.1 什么是make/Makefile?1.2 快速体验 二、深入理解核心机制2.1 依赖关系与依赖方法2.2 伪目标的妙用2.3 具体语法a.makefile的基本雏形b.makefile推导原则! 三、更加具有通用型的makefile1. 变量定义…...
Jenkins 配置python项目和allure
Jenkins新建项目 新建ry-api-auto-test。 添加项目描述,选择gitee令牌。 源码管理,设置仓库地址和凭证。参考我上一篇文章的链接:配置gitee私人令牌和凭证 构建步骤,因为我Jenkins部署在Windows,因此选择batch。…...
优化 Docker 镜像 技巧
优化 Docker 镜像可以提高构建速度、减少镜像大小、提高安全性和效率。以下是一些优化 Docker 镜像的方法: 使用适当的基础镜像 选择合适的基础镜像可以减小镜像大小,并确保基础镜像的安全性和更新性。Alpine、Ubuntu Minimal 等轻量级基础镜像是常用选…...
从简单场景认识建造者模式
建造者设计模式总的来说常见的形式无非就两种。 一种是具体产物样式多,故通过中间者(指挥者)来统筹决定产生哪种对象(组装电脑,都是电脑,只是参数配置不同)。 一种是构造的可选参数多…...
Maven工具学习使用(四)——仓库
仓库分类 对于Mavne来说,仓库只分为两类:本地仓库和远程仓库。当Maven根据坐标查询寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在此构件,或者需要查看是否有更新的构件版本,Maven就会去远程仓库查找,发现需要的构件之后…...
vue3:十一、主页面布局(进入指定菜单页面,默认锁定到左侧菜单)
一、效果 直接进入home页面,直接展开对应的菜单项 二、具体实现 1、菜单容器增加默认选中变量 在菜单容器中将默认展开菜单default-openeds修改为默认选中菜单default-active 2、引入useRoute方法 引入该方法为了获取当前页面的路径 import { useRoute } from …...
linux,防火墙,firewall,常用命令
文章目录 1. 查看防火墙状态2. 查看当前开放的端口和服务查看所有开放的端口查看所有允许的服务查看所有区域的详细信息 3. 开放指定端口开放端口(临时生效)开放端口(永久生效)开放指定端口范围 4. 删除指定端口删除端口ÿ…...
SQL 函数
SQL 函数 概述 SQL 函数是数据库查询语言(Structured Query Language)的核心组成部分之一。它们是用于执行特定任务的预定义过程,可以在查询中使用以增强查询的灵活性和功能性。SQL 函数可以分为两大类:内置函数和用户自定义函数…...
【蓝桥杯】每日练习 Day13
前言 今天做了不少题,但是感觉都太水了,深思熟虑之下主播决定拿出两道相对不那么水的题来说一下(其实还是很水)。 两道问题,一道是日期问题(模拟),一道是区间合并问题。 日期差值 …...
【Docker系列七】Docker Compose 命令详解
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
【AI学习】Transformer 模型
1,概念 是一种基于自注意力机制(Self-Attention Mechanism)的深度学习架构,在自然语言处理、计算机视觉等多个领域都有着极为重要的应用。 2,基本结构 1)编码器(Encoder) 通常由多个相同的编码器层堆叠而成。 每个编码器层包含了多头自注意力机制、前馈神经网络以及…...
大数据学习栈记——HBase操作(shell java)
本文介绍HBase在shell终端的常见操作以及如何利用java api操作HBase,操作系统:Ubuntu24.04 参考: https://blog.51cto.com/u_16099228/8016429 https://blog.csdn.net/m0_37739193/article/details/73618899 https://cloud.tencent.com/d…...
React多层级对象改变值--immer
reduxjs/toolkit底层就是immer,,,所以在使用redux的时候,直接赋值,就会响应式的数据 如果不使用reduxjs/toolkit,可以自己使用immer来实现 安装immer npm install immer引入produce函数,,prod…...
服务器硬盘爆满100%问题解决
问题 在工作中遇到一个服务器,服务器硬盘100%,查找哪个目录文件中占用大量空间。发现加起来才150G,硬盘空间大概有500G。 处理问题,排查是否有某个进程正在删除文件,进程卡住了,所以过滤一下有哪些进程&am…...
智能制造:物联网和自动化之间的关系
工业自动化 工业自动化是机器设备或生产过程在不需要人工直接干预的情况下按预期的目标实现测量、操纵等信息处理和过程控制的统称。 在传统的工业生产过程中,很多环节需要人工操作,比如设备调试、生产监控、质量检测等。然而,随着工业自动化…...
Axure项目实战:智慧城市APP(三)教育查询(显示与隐藏交互)
亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! 课程主题:教育查询 主要内容:教育公告信息,小升初、初升高、高考成绩查询;教育公告信息为传统的信息页面,小升…...
01 设计模式和设计原则
类设计原则: 单一职责原则(Single Responsibility Principle,SRP):实现类要职责单一开闭原则(Open Close Principle,OCP):对扩展开放,对修改关闭里氏替换原则…...
Github 2025-03-23 php开源项目日报Top10
根据Github Trendings的统计,今日(2025-03-23统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10TypeScript项目1JavaScript项目1Shell项目1Laravel: 以优雅语法简化Web开发 创建周期:4028 天开发语言:PHP协议类型:MIT LicenseSt…...
macbook电脑如何清理键盘防止误触
M1芯片的MacBook电脑关机后按任意键开机,是苹果的功能设计。这样设计的目的是为了方便用户,让用户在想要使用电脑时能快速开机。但是清理电脑键盘的时候却成为了一种苦恼 以下是一些清理 MacBook 键盘防止误触的方法: 使用工具锁定键盘 Cle…...
AIMB-ASMB-788B(PPC-MB-620B)RAID驱动安装(笔记版)
创建RAID后安装系统时看不到磁盘信息,以下案例是安装windows10系统时如何安装主板RAID驱动,由于是笔记版不做过多介绍。 RAID驱动链接:https://advdownload.advantech.com.cn/productfile/Downloadfile1/1-2MAHDQD/AIMB-788_788E_RAID_AHCI_…...
深度分页优化思路
深度分页优化思路 思考以下问题 查询以下SQL的流程是怎么样的呢? 为什么只查询10条数据需要7秒? # 查询时间7秒 SELECT * FROM user ORDER BY age LIMIT 1000000, 10问题分析 为什么分页查询随着翻页的深入,会变得越来越慢。 其实࿰…...
K8S学习之基础五十四:jenkins新建测试流水线
jenkins新建测试流水线 新建任务 node(testak) {stage(第1步:从gitee上下载源代码) {git url: "https://gitee.com/akang007/jenkins-sample"script {build_tag sh(returnStdout: true, script: git rev-parse --short HEAD).trim()}}stage(第2步:基…...
HarmonyOS NEXT(九) :图形渲染体系
HarmonyOS NEXT(九) :图形渲染体系 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,可以分享一下给大家。点击跳转到网站。 https://www.captainbed.cn/ccc 文章目录 HarmonyOS NEXT࿰…...
SQLAlchemy关键词搜索技术深度解析:从基础过滤到全文检索
在数据驱动的应用开发中,基于关键词的模糊查询是常见的业务需求。SQLAlchemy作为Python生态中最流行的ORM框架,提供了多种实现关键词搜索的技术方案。本文将从性能、适用场景和技术复杂度三个维度,系统对比分析SQLAlchemy中关键词搜索的最佳实…...
ES数据过多,索引拆分
公司企微聊天数据存储在 ES 中,虽然按照企业分储在不同的ES 索引中,但某些常用的企微主体使用量还是很大。4年中一个索引存储数据已经达到46多亿条数据,占用存储3.1tb, ES 配置 由于多一个副本,存储得翻倍,成本考虑…...
Rust 与 FFmpeg 实现视频水印添加:技术解析与应用实践
引言 在短视频、直播、影视制作等领域,视频水印是一种常见的工具,用于保护版权、提升品牌辨识度或满足合规性要求。然而,开发者在实现水印添加时往往面临以下挑战: 手动处理效率低:使用图像编辑软件(如 P…...
Python语言的游戏物理
Python语言的游戏物理 引言 在现代游戏开发中,物理引擎是一个重要的组成部分,通过模拟真实世界的物理现象,增加了游戏的沉浸感和可玩性。Python作为一种高效、易用的编程语言,虽然在性能方面不如C等语言,但其灵活性和…...
uni-app自动升级功能
效果图 一、VUE login.vue <template><view><view class"uni-common-mt"><view class"uni-flex uni-column"><view class"flex-item flex-item-V"><view class"logo"><image src"/st…...
使用AI一步一步实现若依(26)
功能26:新增一个新员工培训页面 功能25:角色管理 功能24:菜单管理 功能23:从后端获取路由/菜单数据 功能22:用户管理 功能21:使用axios发送请求 功能20:使用分页插件 功能19:集成My…...
逻辑回归(Logistic Regression)模型的概率预测函数
以二分类问题为例,常见的损失函数有 负对数似然损失(neg log-likelihood loss),交叉熵损失(cross entropy loss),deviance loss指数损失(exponential loss)。 前三者虽然名字不同,但却具有相同的表达形式。此外,neg …...
