自动化构建-make/Makefile 【Linux基础开发工具】

文章目录

  • 一、背景
  • 二、Makefile编译过程
  • 三、变量
  • 四、变量赋值
    • 1、"="是最普通的等号
    • 2、“:=” 表示直接赋值
    • 3、“?=” 表示如果该变量没有被赋值,
    • 4、"+="和写代码是一样的,
  • 五、预定义变量
  • 六、函数
    • **通配符**
  • 七、伪目标 .PHONY
  • 八、其他常用功能
    • 代码清理clean
  • 九、嵌套执行Makefile
  • 十、指定头文件路径
  • 十一、指定库文件路径
  • 十二、基本使用
  • 十三、推导过程
  • 十四、适度扩展语法

一、背景

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

二、Makefile编译过程

在这里插入图片描述

Makefile文件中的命令有一定规范,一旦该文件编写好以后在Linux命令行中执行一条make命令即可自动编译整个工程。不同厂家的make可能会稍有不同,并且语法上也有区别,不过基本思想都差不多,主要还是落在目标依赖上,最广泛使用的是GNUmake。

2、语法规则

目标 ... : 依赖 ...命令1命令2. . .

"Makefile的核心规则,类似于一位厨师做菜,目标就是做好一道菜,那么所谓的依赖就是各种食材,各种厨具等等,然后需要厨师好的技术方法类似于命令,才能作出一道好菜。
  同时这些依赖也有可能此时并不存在,需要现场制作,或者是由其他厨师做好,那么这个依赖就成为了其他规则的目标,该目标也会有他自己的依赖和命令。这样就形成了一层一层递归依赖组成了Makefile文件。
  Makefile并不会关心命令是如何执行的,仅仅只是会去执行所有定义的命令,和我们平时直接输入命令行是一样的效果。"

  1. 目标即要生成的文件。如果目标文件的更新时间晚于依赖文件更新时间,则说明依赖文件没有改动,目标文件不需要重新编译。否则会进行重新编译并更新目标文件。
  2. 默认情况下Makefile的第一个目标为终极目标。
  3. 依赖:即目标文件由哪些文件生成。
  4. 命令:即通过执行命令由依赖文件生成目标文件。注意每条命令之前必须有一个tab(此文档编辑器默认是空格,复制下来的代码需要把命令代码的缩进改为tab制表符)保持缩进,这是语法要求。
  5. all:Makefile文件默认只生成第一个目标文件即完成编译,但是我们可以通过all 指定所需要生成的目标文件。
    例如下面的例子:
all: target1 target2 target3
target1:
# 编译规则1
target2:
# 编译规则2
target3:
# 编译规则3

all被设置为第一个目标,并且target1、target2和target3被列为all的依赖。当你在命令行中运行make时,make命令会寻找并执行all目标规则,这将依次执行target1、target2和target3的编译规则。
因此,通过在Makefile中设置all作为默认目标规则,你可以简化构建过程,只需运行make命令即可执行整个编译过程,无需显式指定目标

三、变量

$符号表示取变量的值,当变量名多于一个字符时,使用"( )"
$符的其他用法

$^ 表示所有的依赖文件
$@ 表示生成的目标文件
$< 代表第一个依赖文件

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))ALL: hello.outhello.out: $(OBJ)gcc $< -o $@%.o: %.cgcc -c $< -o $@

四、变量赋值

1、"="是最普通的等号

在Makefile中容易搞错赋值等号,

使用 “=”进行赋值,变量的值是整个Makefile中最后被指定的值。

VIR_A = A
VIR_B = $(VIR_A) B
VIR_A = AA

经过上面的赋值后,最后VIR_B的值是AA B,而不是A B,在make时,会把整个Makefile展开,来决定变量的值(类似于宏定义)

2、“:=” 表示直接赋值

赋予当前位置的值。
VIR_A := A
VIR_B := $(VIR_A) B
VIR_A := AA

最后BIR_B的值是A B,即根据当前位置进行赋值。因此相当于“=”,“:=”才是真正意义上的直接赋值

3、“?=” 表示如果该变量没有被赋值,

赋值予等号后面的值。
VIR ?= new_value

如果VIR在之前没有被赋值,那么VIR的值就为new_value。

VIR := old_value
VIR ?= new_value

这种情况下,VIR的值就是old_value

4、"+="和写代码是一样的,

表示将符号后面的值添加到前面的变量上

五、预定义变量

CC:c编译器的名称,默认值为cc。
cpp c预编译器的名称默认值为$(CC) -E

CC = gcc

回显问题,Makefile中的命令都会被打印出来。如果不想打印命令部分 可以使用@去除回显

@echo "clean done!"

六、函数

通配符

SRC = $(wildcard ./*.c)

匹配目录下所有.c 文件,并将其赋值给SRC变量。

OBJ = $(patsubst %.c, %.o, $(SRC))

这个函数有三个参数,意思是取出SRC中的所有值,然后将.c 替换为.o 最后赋值给OBJ变量。

示例:如果目录下有很多个.c 源文件,就不需要写很多条规则语句了,而是可以像下面这样写

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))ALL: test.exe  #生成执行文件test.exe: $(OBJ)gcc $(OBJ) -o test%.o: %.cgcc -c $< -o $@

这里先将所有.c 文件编译为 .o 文件,这样后面更改某个 .c 文件时,其他的 .c 文件将不在编译,而只是编译有更改的 .c 文件,可以大大提高大项目中的编译速度。

七、伪目标 .PHONY

伪目标只是一个标签,clean是个伪目标没有依赖文件,只有用make来调用时才会执行
当目录下有与make 命令 同名的文件时 执行make 命令就会出现错误。
解决办法就是使用伪目标

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))ALL: test.exetest.exe: $(OBJ)gcc $< -o $@$(OBJ): $(SRC)gcc -c $< -o $@clean:rm -rf $(OBJ) test.exe.PHONY: clean ALL通常也会把ALL设置成伪目标

八、其他常用功能

代码清理clean

我们可以编译一条属于自己的clean语句,来清理make命令所产生的所有文件,列如

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))ALL: test.exetest.exe: $(OBJ)gcc $< -o $@$(OBJ): $(SRC)gcc -c $< -o $@clean:rm -rf $(OBJ) test.exe

九、嵌套执行Makefile

在一些大工程中,会把不同模块或不同功能的源文件放在不同的目录中,我们可以在每个目录中都写一个该目录的Makefile这有利于让我们的Makefile变的更加简洁,不至于把所有东西全部写在一个Makefile中。
  列如在子目录subdir目录下有个Makefile文件,来指明这个目录下文件的编译规则。外部总Makefile可以这样写

subsystem:cd subdir && $(MAKE)
其等价于:
subsystem:$(MAKE) -C subdir

定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较有利于维护。两个例子意思都是先进入"subdir"目录,然后执行make命令
  我们把这个Makefile叫做总控Makefile,总控Makefile的变量可以传递到下级的Makefile中,但是不会覆盖下层Makefile中所定义的变量,除非指定了 "-e"参数。
  如果传递变量到下级Makefile中,那么可以使用这样的声明export
  如果不想让某些变量传递到下级Makefile,可以使用unexport

export variable = value
等价于
variable = value
export variable
等价于
export variable := value
等价于
variable := value
export variable
如果需要传递所有变量,那么只要一个export就行了。后面什么也不用跟,表示传递所有变量

十、指定头文件路径

一般都是通过"-I"(大写i)来指定,假设头文件在:

/home/develop/include

则可以通过-I指定:

-I/home/develop/include

将该目录添加到头文件搜索路径中
在Makefile中则可以这样写:

CFLAGS=-I/home/develop/include

然后在编译的时候,引用CFLAGS即可,如下

yourapp:*.cgcc $(CFLAGS) -o yourapp

十一、指定库文件路径

与上面指定头文件类似只不过使用的是"-L"来指定

LDFLAGS=-L/usr/lib -L/path/to/your/lib

告诉链接器要链接哪些库文件,使用"-l"(小写L)如下:

LIBS = -lpthread -liconv

十二、基本使用

实例代码

#include <stdio.h> 
int main() 
{ printf("hello Makefile!\n"); return 0;
} 

Makefile文件

myproc:myproc.cgcc -o myproc myproc.c.PHONY:clean
clean:rm -f myproc 

依赖关系
• 上面的文件myproc,它依赖myproc.c
依赖方法
gcc -o myproc myproc.c ,就是与之对应的依赖关系
项目清理
• 工程是需要被清理的
• 像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令⸺“make clean”,以此来清除所有的目标文件,以便重编译。
• 但是一般我们这种clean的目标文件,我们将它设置为伪目标,用 .PHONY 修饰,伪目标的特性是,总是被执行的。
• 可以将我们的 hello 目标文件声明成伪目标,测试一下。
什么叫做总是被执行?

$ stat XXXFile: ‘XXX’Size: 987  Blocks: 8  IO Block: 4096  regular file
Device: fd01h/64769d Inode: 1321125 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1000/ whb) Gid: ( 1000/ whb)
Access: 2024-10-25 17:05:30.430619002 +0800
Modify: 2024-10-25 17:05:25.940595116 +0800
Change: 2024-10-25 17:05:25.940595116 +0800⽂件 = 内容 + 属性
Modify: 内容变更,时间更新
Change:属性变更,时间更新
Access:常指的是⽂件最近⼀次被访问的时间。在Linux的早期版本中,每当⽂件被访问时,其atime都会更新。但这种机制会导致⼤量的IO操作。具体更新原则,不做过多解释。

在这里插入图片描述
📌 结论:
.PHONY:让make忽略源文件和可执行目标文件的M时间对比

十三、推导过程

myproc:myproc.o gcc myproc.o -o myproc 
myproc.o:myproc.sgcc -c myproc.s -o myproc.o
myproc.s:myproc.i gcc -S myproc.i -o myproc.s
myproc.i:myproc.c gcc -E myproc.c -o myproc.i .PHONY:clean 
clean: rm -f *.i *.s *.o myproc

编译

$ make
gcc -E myproc.c -o myproc.i
gcc -S myproc.i -o myproc.s
gcc -c myproc.s -o myproc.o
gcc myproc.o -o myproc

在这里插入图片描述

make是如何工作的,在默认的方式下,也就是我们只输入make命令。那么:

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到 myproc 这个文件,并把这个文件作为最终的目标文件。
  3. 如果 myproc 文件不存在,或是 myproc 所依赖的后面的 myproc.o 文件的文件修改时间要比 myproc 这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成myproc 这个文件。
  4. 如果 myproc 所依赖的 myproc.o 文件不存在,那么 make 会在当前文件中找目标为myproc.o 文件的依赖性,如果找到则再根据那一个规则生成 myproc.o 文件。(这有点像一个堆栈的过程)
  5. 当然,你的C文件和H文件是存在的啦,于是 make 会生成 myproc.o 文件,然后再用 myproc.o 文件声明 make 的终极任务,也就是执行文件 hello 了。
  6. 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
  7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。
  8. make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

十四、适度扩展语法

BIN=proc.exe  # 定义变量
CC=gcc
#SRC=$(shell ls *.c)   # 采⽤shell命令⾏⽅式,获取当前所有.c⽂件名
SRC=$(wildcard *.c)   # 或者使⽤ wildcard 函数,获取当前所有.c⽂件名
OBJ=$(SRC:.c=.o)   # 将SRC的所有同名.c 替换 成为.o 形成⽬标⽂件列表
LFLAGS=-o   # 链接选项
FLAGS=-c   # 编译选项
RM=rm -f   # 引⼊命令$(BIN):$(OBJ)@$(CC) $(LFLAGS) $@ $^  # $@:代表⽬标⽂件名。 $^: 代表依赖⽂件列表@echo "linking ... $^ to $@"
%.o:%.c # %.c 展开当前⽬录下所有的.c。 %.o: 同时展开同
名.o@$(CC) $(FLAGS) $< # %<: 对展开的依赖.c⽂件,⼀个⼀个的交给gcc。@echo "compling ... $< to $@" # @:不回显命令.PHONY:clean
clean:$(RM) $(OBJ) $(BIN)   # $(RM): 替换,⽤变量内容替换它.PHONY:test
test:@echo $(SRC)@echo $(OBJ)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/12008.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Three.js+React】教程001:绘制简单的盒子

文章目录 React整合Three.js创建项目绘制一个简单的盒子添加坐标辅助器React整合Three.js 在 React 中结合 Three.js 进行 3D 开发,可以使用 React + Three.js + @react-three/fiber 进行高效渲染,同时配合 @react-three/drei 提供的封装工具,让开发更加简洁。 创建项目 …

K8S集群架构及主机准备

本次集群部署主机分布K8S集群主机配置主机静态IP设置主机名解析ipvs管理工具安装及模块加载主机系统升级主机间免密登录配置主机基础配置完后最好做个快照备份 2台负载均衡器 Haproxy高可用keepalived3台k8s master节点5台工作节点(至少2及以上)本次集群部署主机分布 K8S集群主…

SQL入门到精通 理论+实战 -- 在 MySQL 中学习SQL语言

目录 一、环境准备 1、MySQL 8.0 和 Navicat 下载安装 2、准备好的表和数据文件&#xff1a; 二、SQL语言简述 1、数据库基础概念 2、什么是SQL 3、SQL的分类 4、SQL通用语法 三、DDL&#xff08;Data Definition Language&#xff09;&#xff1a;数据定义语言 1、操…

3.5.7 基于横盘结构的分析体系——缠论(背驰/背离)

背离&#xff08;背驰&#xff09; 本文讨论背离主要从量价和时空的角度来讨论。涉及的背离类型如下表&#xff1a; 角度 类型 成交量和价格 量价背离 时间和空间 MACD背离 笔背离 盘整背离 趋势背离 表1-9 背离的角度和类型。 从成交量和价格的角度&#xff0c;本文…

51c嵌入式~电路~合集25

我自己的原文哦~ https://blog.51cto.com/whaosoft/13241709 一、“开关电源”和“普通电源”的区别 什么叫开关电源 随着电力电子技术的发展和创新&#xff0c;使得开关电源技术也在不断地创新。目前&#xff0c;开关电源以小型、轻量和高效率的特点被广泛应用几乎所有的电…

深度学习 Pytorch 基础网络手动搭建与快速实现

为了方便后续练习的展开&#xff0c;我们尝试自己创建一个数据生成器&#xff0c;用于自主生成一些符合某些条件、具备某些特性的数据集。 导入相关的包 # 随机模块 import random# 绘图模块 import matplotlib as mpl import matplotlib.pyplot as plt# 导入numpy import nu…

【文件上传】

目录 一. 介绍二. 本地存储三. 阿里云OSS3.1 准备工作3.2 入门程序3.3 案例集成3.4 程序优化 \quad 一. 介绍 \quad 三要素缺一不可 \quad 二. 本地存储 \quad 解决相同命名覆盖问题 \quad 三. 阿里云OSS \quad \quad 3.1 准备工作 \quad \quad 3.2 入门程序 \quad \quad 3.3…

Deepseek-R1 和 OpenAI o1 这样的推理模型普遍存在“思考不足”的问题

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Vue3的el-table-column下拉输入实时查询API数据选择的实现方法

由于本人对el-table-column有下拉输入选择的要求&#xff0c;根据网上搜索的资料及本人优化&#xff0c;推出我比较满意的方法&#xff0c;供各位读者参考使用。 效果图 el-table-column写法 <el-table-columnlabel"货品编号"align"center"prop"…

Electron使用WebAssembly实现CRC-8 MAXIM校验

Electron使用WebAssembly实现CRC-8 MAXIM校验 将C/C语言代码&#xff0c;经由WebAssembly编译为库函数&#xff0c;可以在JS语言环境进行调用。这里介绍在Electron工具环境使用WebAssembly调用CRC-8 MAXIM格式校验的方式。 CRC-8 MAXIM校验函数WebAssembly源文件 C语言实现C…

使用 Elastic Cloud Hosted 优化长期数据保留:确保政府合规性和效率

作者&#xff1a;来自 Elastic Jennie Davidowitz 在数字时代&#xff0c;州和地方政府越来越多地承担着管理大量数据的任务&#xff0c;同时确保遵守严格的监管要求。这些法规可能因司法管辖区而异&#xff0c;通常要求将数据保留较长时间 —— 有时从一年到七年不等。遵守刑事…

安卓(android)饭堂广播【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的&#xff08;如果代码有错漏&#xff0c;可查看源码&#xff09; 1.熟悉广播机制的实现流程。 2.掌握广播接收者的创建方式。 3.掌握广播的类型以及自定义官博的创建。 二、实验条件 熟悉广播机制、广播接收者的概念、广播接收者的创建方式、自定广播实现方式以及有…

OPENPPP2 —— VMUX_NET 多路复用原理剖析

在阅读本文之前&#xff0c;必先了解以下几个概念&#xff1a; 1、MUX&#xff08;Multiplexer&#xff09;&#xff1a;合并多个信号到单一通道。 2、DEMUX&#xff08;Demultiplexer&#xff09;&#xff1a;从单一通道分离出多个信号。 3、单一通道&#xff0c;可汇聚多个…

【Linux】从硬件到软件了解进程

个人主页~ 从硬件到软件了解进程 一、冯诺依曼体系结构二、操作系统三、操作系统进程管理1、概念2、PCB和task_struct3、查看进程4、通过系统调用fork创建进程&#xff08;1&#xff09;简述&#xff08;2&#xff09;系统调用生成子进程的过程〇提出问题①fork函数②父子进程关…

Deep Crossing:深度交叉网络在推荐系统中的应用

实验和完整代码 完整代码实现和jupyter运行&#xff1a;https://github.com/Myolive-Lin/RecSys--deep-learning-recommendation-system/tree/main 引言 在机器学习和深度学习领域&#xff0c;特征工程一直是一个关键步骤&#xff0c;尤其是对于大规模的推荐系统和广告点击率预…

AI智慧社区--Excel表的导入导出

Excel表导入导出的环境配置 1.导入依赖 <dependency><groupId>cn.afterturn</groupId><artifactId>easypoi-spring-boot-starter</artifactId><version>${easypoi.version}</version></dependency>2.配置Excel的导入导出以及…

【C++】B2122 单词翻转

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 &#x1f4af;一、我的做法代码实现&#xff1a;代码解析思路分析 &#x1f4af;二、老师的第一种做法代码实现&a…

【流媒体】搭建流媒体服务器

搭建Windows Nginx服务器 搭建 下载nginx工具包解压至本地&#xff0c;并在cmd窗口中切换至nginx所在的本地目录修改 conf/nginx.conf 文件&#xff0c;更改其端口号 server中的 listen的端口号从 80改为 8080&#xff0c;因为80经常被其他服务占用&#xff0c;导致无法打开 …

编程AI深度实战:给vim装上AI

系列文章&#xff1a; 编程AI深度实战&#xff1a;私有模型deep seek r1&#xff0c;必会ollama-CSDN博客 编程AI深度实战&#xff1a;自己的AI&#xff0c;必会LangChain-CSDN博客 编程AI深度实战&#xff1a;给vim装上AI-CSDN博客 编程AI深度实战&#xff1a;火的编程AI&…

MySQL锁详解

MySQL锁详解 数据库的锁机制锁的分类行级锁与表级锁行级锁之共享锁与排他锁乐观锁与悲观锁悲观锁乐观锁 Innodb存储引擎的锁机制行级锁与表级锁的使用区分三种行锁的算法死锁的问题多版本并发控制MVCC 数据库的锁机制 什么是锁&#xff1f;锁是一种保障数据的机制 为何要用锁…