当前位置: 首页 > news >正文

make学习三:书写规则

系列文章目录

Make学习一:make初探

Make学习二:makefile组成要素

文章目录

  • 系列文章目录
  • 前言
  • 默认目标
  • 规则语法
  • order-only prerequisites
  • 文件名中的通配符
  • 伪目标 Phony Targets
  • 没有 Prerequisites 和 recipe
  • 内建特殊目标名
  • 一个目标多条规则或多个目标共享 prerequisites
  • 自动生成依赖
  • 总结


前言

前文 Make学习二:makefile组成要素 中提到,Makefile 组成中的重要成员就是显示规则。本文重点在编写规则上。

显示规则的格式如下:

target ... : prerequisites ...recipe...

引用前面 Make学习一:make初探 中的话,简单来说,一条规则就像一道菜。如果说 target 是我们的西红柿炒鸡蛋,那 prerequisites 就是西红柿炒鸡蛋的依赖,或者说输入,简单点就是西红柿和鸡蛋。而 recipe 就是西红柿炒鸡蛋的菜谱,也就是要做成这道菜,需要什么步骤,每一步要怎么做。 实际上,recipe 确实是菜谱的意思,prerequisites 意为先决条件。

默认目标

每一条规则在 Makefile 中的 书写顺序一般不影响功能,但会决定默认目标(default goal),即如果你运行 make 没有指定目标,默认会构建的那个目标。

默认目标的规则是:取第一个 Makefile 里第一条规则的第一个 target。

因此,通常会把整个程序编译的规则写在 Makefile 最前面,这个规则的目标经常叫做 all。类似,make 的约定。

如下示例,直接运行 make 输出 Hello, world!:

all: hellohello:@echo "Hello, world!"

规则语法

一条规则的基本写法是:

target ... : prerequisites ...recipe...

targets 一般是文件名,可以用空格分隔多个目标。也可以用 通配符(wildcards)。
配方(recipe)行必须以 Tab 开头(这一点非常重要!)。 也可以通过设置 .RECIPEPREFIX 变量来改成别的符号

一条规则可以有:

  • 多个依赖(prerequisites);
  • 多个目标(targets);
  • 也可以没有依赖(比如 .PHONY 规则);
  • 或者没有 recipe(表示目标是伪目标或不需要命令)。

多个依赖示例:

edit: main.o kbd.o command.o display.ogcc -o edit main.o kbd.o command.o display.o

多个目标示例:

clean temp:rm -f *.o temp

这里 clean 和 temp 是 两个目标,共享同一个配方。

长行可以用 \ 续行,但 recipe 不需要续行,必须每行一个 Tab。

规则的作用:如果目标不存在或目标的修改时间早于任何一个依赖,则按照 recipe 去更新它们

order-only prerequisites

有时你会想要某个依赖在目标之前被构建,但不希望因为这个依赖的更新导致目标被重建。这就需要用 order-only prerequisite(仅顺序依赖)。

简单一句话概括:该依赖必须存在,但同时它的更新不能影响到 target 的更新。那么你就需要 order-only prerequisite

写法:target : normal-prerequisites | order-only-prerequisites 使用管道符把 order-only-prerequisites 放在右边即可。

例如:我们想把当前目录下的 foo.c 和 bar.c 文件构建出来的 foo.o 和 bar.o 放在 obj 目录中。但这个目录可能一开始不存在。我们需要:先保证 obj/ 目录存在;但不要因为 obj/ 目录变化而导致目标重建。则 Makefile 的内容如下所示:

OBJDIR := obj
OBJS := $(OBJDIR)/foo.o $(OBJDIR)/bar.oall: $(OBJS)# 创建 obj/ 目录(order-only)
$(OBJDIR)/%.o : %.c | $(OBJDIR)@echo "Compiling $< to $@"@cp $< $@# 仅顺序依赖:保证 obj/ 目录存在,但不影响重建判断
$(OBJDIR):@echo "Creating directory $@"@mkdir -p $@

第一次执行 make,输出如下:

Creating directory obj
Compiling foo.c to obj/foo.o
Compiling bar.c to obj/bar.o

即便我们在 obj 目录下通过命令touch obj/temp创建文件,执行 make 依旧不会有任何更新:make: 对“all”无需做任何事。

文件名中的通配符

在 Makefile 里,一个文件名可以使用通配符(wildcard characters)来匹配多个文件。支持的通配符和 Linux shell 里的一样:* 匹配 任意长度的任意字符,? 匹配任意单个字符,[abc] 匹配括号中任意一个字符。~(波浪号)在文件名开头(单独出现或后跟 /)代表当前用户根目录。

一条规则分 target,prerequisite 和 recipe。target 和 prerequisite 中的 通配符由 make 来展开。而 recipe 中的通配符会把命令传递给 shell 由 shell 来处理。

例如:以下 Makefile 执行 make 之后会打印出所有更新之后的 .c 文件内容。$? = 依赖中比目标 print 新的文件。

print: *.c@echo $?

但变量中的通配符不会自动展开,需要使用函数 $(wildcard)。 例如:objects = *.o 中变量赋值并不会展开,objects 只是 “*.o” 字符串。

此时,当我们通过以下 Makefile 来执行 make 命令:

objects = *.ofoo : $(objects)cc -o foo $(CFLAGS) $(objects)

若当前目录下没有任何 .o 文件,则 *.o 就不会展开,则会出错:make: *** 没有规则可制作目标“*.c”,由“*.o” 需求。 停止。

为了解决这个问题,则需要在变量定义时则进行通配符展开为文件名列表。使用 wildcard 函数,格式:$(wildcard pattern...)

objects := $(wildcard *.o)
foo : $(objects)  cc -o foo $(CFLAGS) $(objects)

若当前目录下没有任何 .o 文件,则 *.o 仍旧会展开,只是变量 objects 为空字符串。报错:cc -o foo cc: fatal error: no input files

伪目标 Phony Targets

所谓 伪目标(phony target),指的是根本就不是某个实际文件名的目标,而是一个用于明确执行某个配方(recipe)的名字。

使用伪目标的两个主要原因是:避免与同名文件冲突 和 提高性能。例如,如下 Makefile 内容:

clean:rm *.o temp

上面例子中,clean 不是实际会被创建的文件。因此,每次运行 make clean 时,都会执行 rm 命令。

如果当前目录里真有一个名字叫 clean 的文件,make clean 就会误以为 clean 已是最新,不执行删除操作

使用 .PHONY: clean 后,clean 的配方 总是会执行,不会被任何文件阻止。

没有 Prerequisites 和 recipe

如果一个规则 既没有依赖(prerequisite)也没有配方(recipe),并且它的目标文件不存在,那么每次 make 运行时都会认为这个目标已经“更新”。这意味着:所有依赖于这个目标的规则,每次都会执行它们自己的配方。

这个机制常用于强制重新构建(不论文件是否真正变化)。例如以下示例:

clean: FORCErm $(objects)FORCE:

内建特殊目标名

有一些目标名称如果出现在 Makefile 中,会具有特殊的含义。这些名字通常以 . 开头。

.PHONY(伪目标声明),.PHONY 后面列出的所有目标都被认为是伪目标(phony targets)。

.SUFFIXES 目标后面列出的是用于后缀规则 suffix rules的后缀列表。可以用 .SUFFIXES: 清除所有后缀规则(这样就不会自动匹配如 .c → .o 这些规则了)。

.DEFAULT(没有匹配规则时的最后手段),如果一个目标没有任何匹配的规则(无显式也无隐式规则),就会使用 .DEFAULT 提供的配方。

.DEFAULT:@echo "No rule for $@"

等等等等

一个目标多条规则或多个目标共享 prerequisites

例如:kbd.o command.o files.o: command.h 这条 Makefile 内容中,多个目标同时依赖同一个头文件。此时可以用 $@ 来区分当前到底是哪个目标。

一个文件可以同时是多条规则的目标(target)。来自不同规则的所有依赖(prerequisites)会合并成一个总的依赖列表。只要目标比任何一个依赖旧,就会执行配方(recipe)。

例如以下 Makefile 的内容:

objects = foo.o bar.ofoo.o : defs.hbar.o : defs.h test.h$(objects) : config.h

foo.o 的依赖会合并成一个列表:config.h + defs.h

自动生成依赖

在程序的 Makefile 中,你通常需要写很多这种规则:main.o: defs.hmain.c 里用了 #include "defs.h",那么你就得写 main.o: defs.h

你写这个规则是为了让 make 知道:如果 defs.h 变了,就得重新编译 main.o。如果你的程序很大,#include 很多,每次增加或删掉头文件都得手动改 Makefile,非常麻烦,容易出错。

为了解决这个问题,现代 C 编译器可以自动帮你生成这些依赖规则。它们会扫描你的源码里的 #include 语句,通常使用 -M 选项来实现。例如:cc -M main.c 它会输出:main.o : main.c defs.h 所以你不再需要自己写这些规则,编译器会帮你搞定。

总结

完结撒花!!

相关文章:

  • 【质量管理】TRIZ(萃智)的工程系统进化法则
  • 交叉编译tcpdump静态编译单个文件
  • DHCP 服务器运行流程图
  • NHANES指标推荐:BUCR
  • uniapp-商城-40-shop 购物车 选好了 进行订单确认4 配送方式3 地址编辑
  • Spring_MVC 中的 JSON 数据处理与 REST 风格开发
  • 图论---最大流(Dinic)
  • Lua 第11部分 小插曲:出现频率最高的单词
  • 《MySQL 技术内幕-innoDB 存储引擎》笔记
  • 顶会招牌idea:机器学习+组合优化 优秀论文合集
  • 博物馆除湿控湿保卫战:M-5J1R 电解除湿科技如何重塑文物守护的未来
  • 服务器数据备份,服务器怎么备份数据呢?
  • 现代多核调度器的本质 调度三重奏
  • 设计模式--桥接模式详解
  • 1.1 道路结构特征
  • 驱动开发硬核特训 · Day 22(上篇): 电源管理体系完整梳理:I2C、Regulator、PMIC与Power-Domain框架
  • c++多线程初识
  • apkpure 谷歌插件 下载的apk包
  • RISC-V MCU定时器架构与低功耗设计
  • OpenStack私有云详细介绍
  • 上海乐高乐园建设进入最后冲刺,开园限量纪念年卡将于5月开售
  • 吉林省公安厅出入境管理总队政委明志全已任省安保集团总经理
  • 银川市市长信箱被指已读乱回,官方回应
  • 戴昕谈隐私、数据、声誉与法律现实主义
  • 中公教育:去年全面扭亏,经营性现金流增长169.6%
  • 如何做大中国拳击产业的蛋糕?这项赛事给出办赛新思考