Linux Makefile变量详解

news2025/1/21 10:14:24

前言

我们是地球人。曾经为复杂的 Makefile 变量而苦恼过吗?这就是我们的用武之地。我们简化您的构建流程,以获得更快、更高效的结果。看看我们。

自 1976 年出现以来,Make 一直在帮助开发人员自动执行编译代码、构建可执行文件和生成文档的复杂流程。

与其他编程语言一样,Make 允许您定义和使用有助于值重用的变量。

您是否发现自己在多个地方使用相同的值?这既重复又容易出错。如果您想更改此值,则必须在所有地方更改它。这个过程很乏味,但是可以用变量来解决,并且Make提供了强大的变量操作技术,可以让你的生活更轻松。

在本文中,您将了解有关 make 变量以及如何使用它们的所有信息。

什么是 Makefile 变量

变量是一个命名构造,可以保存可以在程序中重用的值。它的定义方式是先写一个名称,后跟 =:=::= ,然后写一个值。变量的名称可以是除“:”、“#”、“=”或空格之外的任何字符序列。此外,与许多其他编程语言一样,Makefile 中的变量名称区分大小写。

以下是变量定义的示例:

foo = World

变量值之前的所有空格都将被删除,但末尾的空格将被保留。允许在变量值内使用 $ ,但 makefile 将假定以 $ 符号开头的字符串引用另一个变量,并将替换变量的值:

foo = one$two
# foo becomes onewo

您很快就会了解到, makefile 假定 $t 引用另一个名为 t 的变量并替换它。由于 t 不存在,它是空的,因此 foo 变成 onewo 。如果您想逐字包含 $ ,则必须使用另一个 $ 对其进行转义:

foo = one$$two

如何使用Makefile变量

一旦定义,变量就可以在任何目标、先决条件或配方中使用。要替换变量的值,您需要使用美元符号 ( $ ),后跟括号或花括号中的变量名称。例如,您可以使用 ${foo}$(foo) 引用 foo 变量。

以下是配方中变量引用的示例:

foo = World
all:
    echo "Hello, $(foo)!"

与之前的 makefile 一起运行 make 将打印“Hello, World!”。

变量使用的另一个常见示例是在编译 C 程序时,您可以定义一个 objects 变量来保存所有目标文件的列表:

objects = main.o foo.o bar.o
program : $(objects) # objects used in prerequisite
    cc -o program $(objects) # objects used in recipe

$(objects) : foo.h # objects used in target

此处, objects 变量已在目标、先决条件和配方中使用。

与许多其他编程语言不同,使用未显式设置的变量不会导致错误;相反,该变量将使用空字符串作为其默认值。但是,一些特殊变量具有内置的非空值,并且其他几个变量为每个不同的规则设置了不同的默认值(稍后会详细介绍)。

如何设置变量

设置变量是指定义一个变量的初始值以及稍后在程序中更改其值。您可以在 makefile 中显式设置一个值,也可以将其作为环境变量或命令行参数传递。

Makefile 中的变量

您可以通过四种不同的方式在 Makefile 中定义变量:

  1. 递归赋值
  2. 简单的分配
  3. 立即分配
  4. 有条件赋值

递归和简单赋值

您可能还记得,您可以使用 =:=::= 定义变量。根据定义变量所使用的运算符的不同,变量的扩展方式存在细微的差异。

  1. 使用 = 定义的变量称为递归扩展变量,并且
  2. :=::= 定义的变量称为简单扩展变量。

当递归扩展变量被扩展时,其值将被逐字替换。如果替换文本包含对其他变量的引用,它们也会被替换,直到不再遇到变量引用。考虑以下示例,其中 foo 扩展为 Hello $(bar)

foo = Hello $(bar)
bar = World

all:
    @echo "$(foo)"

由于 foo 是一个递归扩展变量,因此 $(bar) 也被扩展,并打印“Hello World”。每次扩展变量时都会使用任何引用变量的当前值执行此递归扩展过程:

bar = World
foo = Hello $(bar)

bar = Make
# foo now expands to "Hello Make"

all:
    @echo ${foo} # prints Hello Make

递归扩展变量的最大优点是它们可以轻松地分段构造新变量:您可以定义变量的单独部分并将它们串在一起。您可以定义更细粒度的变量并将它们连接在一起,这使您可以更好地控制 make 的执行方式。

例如,考虑以下在编译 C 程序时经常使用的代码片段:

CFLAGS = -g
ALL_CFLAGS = -I. $(CFLAGS)
main.o: main.c
    $(CC) -c $(ALL_CFLAGS) main.c

这里, ALL_CFLAGS 是一个递归扩展的变量,它扩展为包含 CFLAGS 的内容以及 -I. 选项。如果您希望在保留强制 -I. 选项的同时传递其他选项,则可以覆盖 CFLAGS 变量:

CFLAGS="-g -Wall" # ALL_CFLAGS expands to "-I. -g -Wall"

递归扩展变量的缺点是无法将某些内容附加到变量的末尾:

CFLAGS = $(CFLAGS) -I. # Causes infinite recursion

为了克服这个问题,GNU Make 支持另一种类型的变量,称为简单扩展变量,它是用 := 或 ::= 定义的。简单扩展的变量在定义时会被扫描以获取更多变量引用,并且它们会被一劳永逸地替换。

与递归扩展变量不同,在递归扩展变量中,引用的变量将扩展为其当前值,在简单扩展变量中,引用的变量将在定义变量时扩展为其值:

bar := World
foo := Hello $(bar)

bar = Make

all:
    @echo ${foo} # Prints Hello World

使用简单的扩展变量,可以实现以下操作:

CFLAGS = $(CFLAGS) -I.

GNU Make 支持简单的递归扩展变量。然而,其他版本的 make 通常只支持递归扩展变量。 2012 年,对简单扩展变量的支持已添加到可移植操作系统接口 (POSIX) 标准中,仅使用 ::= 运算符。

立即分配

:::= 定义的变量称为立即扩展变量。与简单扩展的变量一样,它的值在定义时会立即扩展。但就像递归扩展变量一样,每次使用时都会重新扩展。值立即展开后,会自动加引号,展开后的值中所有 $ 的实例都会转换为 $$

在以下代码中,立即扩展的变量 foo 的行为与简单扩展的变量类似:

bar := World
foo :::= Hello $(bar)

bar = Make

all:
    @echo ${foo} # Prints Hello World

然而,如果有对其他变量的引用,事情就会变得有趣:

var = one$$two
OUT :::= $(var)
var = three$$four

这里, OUT 将具有值 one$$two 。这是因为 $(var) 立即扩展为 one$two ,它被引用以获得 one$$two 。但 OUT 是一个递归变量,所以使用时, $two 会被扩展:

two = two

all:
    @echo ${OUT} # onetwo

POSIX Make 支持 :::= 运算符,但 GNU Make 从 4.4 版本开始包含此运算符。

条件赋值

仅当变量尚未定义时,条件赋值运算符 ?= 才可用于设置变量:

foo = World

foo ?= Make # foo will not change
bar ?= Make # bar will change

all:
    @echo Hello ${foo}
    @echo Hello ${bar}

有条件地定义变量的一种等效方法是使用 origin 函数:

foo ?= Make

# is equivalent to

ifeq ($(origin foo), undefined)
foo = Make
endif

这四种类型的分配可以在某些特定情况下使用:

外壳分配

有时您可能需要运行 shell 命令并将其输出分配给变量。您可以使用 shell 函数来做到这一点:

files = $(shell ls) # Runs the `ls` command & assigns its output to `files`

其简写是 shell 赋值运算符 != 。对于此运算符,右侧必须是 shell 命令,其结果将分配给左侧:

files != ls

带空格的变量

变量定义末尾的尾随空格保留在变量值中,但开头的空格将被去除:

foo = xyz   # There are spaces at the beginning and at the end

# Prints "startxyz   end"
all:
    @echo "start${foo}end"

可以通过使用第二个变量来存储空格字符来保留开头的空格:

nullstring =
foo = ${nullstring} xyz   # Spaces at the end

# Prints "start xyz   end"
all:
    @echo "start${foo}end"

目标特定变量

可以将变量的范围限制为仅针对特定目标。其语法如下:

target … : variable-assignment

这是一个例子:

target-one: foo = World
target-two: foo = Make

target-one:
    @echo Hello ${foo}

target-two:
    @echo Hello ${foo}

这里,变量 foo 将根据当前评估的目标 make 具有不同的值:

特定于模式的变量

在这里插入图片描述

特定于模式的变量可以将变量的范围限制为与特定模式匹配的目标。语法类似于特定于目标的变量:

pattern … : variable-assignment

例如,以下行将任何以 .c 结尾的目标的变量 foo 设置为 World

%.c: foo = World

当您想要为共享公共模式的多个目标设置变量时,通常会使用特定于模式的变量,例如为所有 C 文件设置相同的编译器选项。

环境变量

当您将 make 变量与环境变量配对时,它们的真正威力就开始显现。当 make 在 shell 中运行时,shell 中存在的任何环境变量都会转换为具有相同名称和值的 make 变量。这意味着您不必在 makefile 中显式设置它们:

all:
    @echo ${USER}

当您运行之前的 makefile 时,它应该打印您的用户名,因为 USER 环境变量存在于 shell 中。

此功能最常与标志一起使用。例如,如果您使用首选 C 编译器选项设置 CFLAGS 环境变量,则大多数 makefiles 将使用它们来编译 C 代码,因为按照惯例, 如果 makefile 中对变量进行显式赋值,它将覆盖任何同名的环境变量:

USER = Bob

all:
    @echo ${USER}

较早的 makefile 将始终打印 Bob ,因为赋值会覆盖 $USER 环境变量。您可以将 -e 标志传递给 make ,以便环境变量覆盖分配,但不建议这样做,因为它可能会导致意外结果。

命令行参数

您可以将变量值作为命令行变量传递给 make 命令。与环境变量不同,命令行参数将始终覆盖 makefile 中的赋值,除非使用 override 指令:

override FOO = Hello
BAR = World

all:
    @echo "${FOO} ${BAR}"

您可以简单地运行 make ,并且将使用默认值:

$ make
Hello World

您可以通过将 BAR 作为命令行参数传递来传递新值:

$ make BAR=Make
Hello Make

但是,由于 override 指令与 FOO 一起使用,因此无法通过命令行参数更改它:

$ make FOO=Hi
Hello World

此功能很方便,因为它允许您更改变量的值而无需编辑 makefile 。这最常用于传递可能因系统而异的配置选项或用于自定义软件。作为一个实际示例,Vim 使用命令行参数来覆盖配置选项,例如运行时目录和默认配置的位置。

如何追加到变量

在这里插入图片描述

您可以使用简单扩展变量的先前值向其中添加更多文本:

foo := Hello
foo := ${foo} World

# prints "Hello World"
all:
    @echo ${foo}

如前所述,此语法将通过递归扩展变量产生无限递归错误。在这种情况下,您可以使用 += 运算符,它将文本附加到变量,并且它可用于递归扩展和简单扩展变量:


foo = Hello
foo += World

bar := Hello
bar += World

# Both print "Hello World"
all:
    @echo ${foo}
    @echo ${bar}

但是,它对于两种不同类型的变量的工作方式存在细微的差异,您可以在文档中阅读有关内容。

如何使用特殊变量

在 Make 中,任何未定义的变量都会分配一个空字符串作为默认值。然而,有一些特殊变量是例外:

自动变量

自动变量是特殊变量,其值是根据特定规则的目标和先决条件自动设置的。以下是几个常用的自动变量:

  1. $@ 是规则目标的文件名。
  2. $< 是第一个先决条件的名称。
  3. $? 是比目标新的所有先决条件的名称,它们之间有空格。如果目标不存在,则将包含所有先决条件。
  4. $^ 是所有先决条件的名称,它们之间有空格。

这是一个显示自动变量实际作用的示例:

hello: one two
    @echo $@
    @echo $<
    @echo $?
    @echo $^

    @touch hello

one:
    @touch one

two:
    @touch two

clean:
    @rm -f hello one two

与之前的 makefile 一起运行 make 会打印以下内容:

hello
one
one two
one two

如果您运行 touch one 修改 one 并再次运行 make ,您将得到不同的输出:

hello
one
one
one two

由于 one 比目标 hello 新,因此 $? 仅包含 one

这些自动变量存在变体,可以从匹配的表达式中提取目录和目录内文件名称。您可以在官方文档中找到所有自动变量的列表。

当目标和先决条件名称指示配方如何执行时,通常会使用自动变量。一个非常常见的实际示例是以下规则,它将 x.c 形式的 C 文件编译为 x.o

%.o:%.c
    $(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@

隐式变量

为编译系统制作一些常用操作的预定义规则。这些规则包括以下内容

  1. 使用 $(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@ 形式的规则将 x.c 编译为 x.o
  2. 使用 $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@ 形式的规则编译 x.ccx.cpp
  3. 链接静态对象文件 x.o 以使用 $(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS) 形式的规则创建 x
  4. 还有很多

这些隐式规则利用某些称为隐式变量的预定义变量。其中一些如下:

  1. CC 是编译C程序的程序。默认为 cc
  2. CXX 是编译C++程序的程序。默认为 g++
  3. CPP 是运行C预处理器的程序。默认为 $(CC) -E
  4. LEX 是将Lex语法编译成源代码的程序。默认为 lex 。
  5. YACC 是将Yacc语法编译成源代码的程序。默认为 yacc 。

您可以在 GNU Make 的文档中找到隐式变量的完整列表。

就像标准变量一样,您可以显式定义隐式变量:

CC = clang

# This implicit rule will use clang as compiler
foo.o:foo.c

标识

标志是特殊变量,通常用于将选项传递给各种命令行工具,例如编译器或预处理器。编译器和预处理器是一些常用工具隐式定义的变量,包括以下内容:

  1. CFLAGS 被传递给 CC 来编译 C。
  2. CPPFLAGS 被传递到 CPP 以预处理 C 程序。
  3. CXXFLAGS 被传递给 CXX 来编译 C++。
作者:岬淢箫声
日期:2023年11月1日
版本:1.0
链接:http://caowei.blog.csdn.net

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ICCV2023】利用软对比学习和全能分类器提升模型在跨域场景发现新类别的能力...

论文标题&#xff1a; Boosting Novel Category Discovery Over Domains with Soft Contrastive Learning and All in One Classifier 论文链接&#xff1a;https://openaccess.thecvf.com/content/ICCV2023/html/Zang_Boosting_Novel_Category_Discovery_Over_Domains_with_So…

【Linux】 su 命令使用

su&#xff08;英文全拼&#xff1a;switch user&#xff09;命令用于变更为其他使用者的身份&#xff0c;除 root 外&#xff0c;需要键入该使用者 的密码。使用权限&#xff1a;所有使用者。 语法 su [选项] [-] [USER [参数]...] su命令 -Linux手册页 著者 作者&#xff1…

第二十二章 LaneAF框架结构以及接入MMDetection3D模型(车道线感知)

一 前言 近期参与到了手写AI的车道线检测的学习中去&#xff0c;以此系列笔记记录学习与思考的全过程。车道线检测系列会持续更新&#xff0c;力求完整精炼&#xff0c;引人启示。所需前期知识&#xff0c;可以结合手写AI进行系统的学习。 二 LaneAF接入openlane数据集 2.1 Lan…

Idea快速生成测试类

例如写写完一个功能类,需要对里面方法进行测试 在当前页面 按住CTRLSHFITT 选择你要生成的测试方法 点击OK,就会在test目录下在你对应包下生成对应测试类

k8s、调度约束

Kubernetes 是通过 List-Watch **** 的机制进行每个组件的协作&#xff0c;保持数据同步的&#xff0c;每个组件之间的设计实现了解耦 用户是通过 kubectl 根据配置文件&#xff0c;向 APIServer 发送命令&#xff0c;在 Node 节点上面建立 Pod 和 Container。 APIS…

axios 实现请求重试

前景提要&#xff1a; ts 简易封装 axios&#xff0c;统一 API 实现在 config 中配置开关拦截器 请求重试的核心是可以重放请求&#xff0c;具体实现就是在 axios 中&#xff0c;拿到当前请求的 config 对象&#xff0c;再用 axios 实例&#xff0c;就能重放请求。 在无感刷新…

MODWT(最大重叠离散小波变换)

MODWT 全称为 “多分辨率离散小波变换”&#xff08;Multiresolution Discrete Wavelet Transform&#xff09;&#xff0c;是一种基于小波分析的数据处理方法。 和传统的小波变换不同&#xff0c;MODWT 使用多种长度的小波滤波器来对信号进行多尺度分解。在 MODWT 中&#xf…

基于C语言实现扫雷小游戏

扫雷游戏 1. 扫雷游戏分析和设计1.1 扫雷游戏的功能说明1.2 游戏的分析和设计1.2.1 数据结构的分析 2. 扫雷游戏的代码实现3. 扫雷游戏的扩展 1. 扫雷游戏分析和设计 1.1 扫雷游戏的功能说明 使用控制台实现经典的扫雷游戏 游戏可以通过菜单实现继续玩或者退出游戏 扫雷的棋…

掌握微信批量添加好友技巧,让你的社交更高效

微信作为当今的热门通讯工具&#xff0c;在企业营销中扮演着越来越重要的角色。然而&#xff0c;微信并没有提供自动批量添加好友的功能&#xff0c;给运营者带来了不小的挑战。一个个手动添加不仅耗时&#xff0c;而且频繁操作还容易导致账号被封。本文将介绍几种手动批量添加…

你担心spring容器中scope为prototype的bean太大内存溢出吗?

你担心spring容器中scope为prototype的bean太大内存溢出吗&#xff1f; 提出假设 之前一直担心spring的scope为prototype的bean在一些高并发的场景下&#xff0c;吃不消吗&#xff0c;甚至会内存溢出&#xff0c;这样的担心不是没有道理的&#xff0c;&#xff08;以下是假设…

垃圾回收与内存分配

文章目录 查看本地虚拟机版本堆空间的基本结构内存分配和回收策略主要进行GC的区域内存分配策略对象优先在Eden区分配大对象直接进入老年代长期存活的对象进入老年代动态对象年龄判定空间分配担保 Full GC的触发条件 如何判断对象可以回收引用计数法可达性分析算法方法区的回收…

Proteus仿真--基于51单片机的走马灯实现(仿真文件+程序)

本文主要介绍基于51单片机的走马灯仿真&#xff08;完整仿真源文件及代码见文末链接&#xff09; 本设计中有16个LED灯用于流水走马演示&#xff0c;一位数码管用于显示当前模式状态&#xff0c;3个按键分别用于选择模式及加减速度控制 仿真图如下 其中 K1&#xff1a;用于模…

上海物理、化学高考命题趋势及2024年上海物理、化学高考备考建议

在上海高考时&#xff0c;物理、化学虽然不像语文、英语和数学那样分数高&#xff0c;但是仍然很重要。那么&#xff0c;从这几年的上海物理、化学的高考题目来看&#xff0c;我们互发现什么命题趋势和考题特点呢&#xff1f;如何备考接下来的2024年高考物理和化学呢&#xff1…

我在Vscode学OpenCV 基本的加法运算

根据上一篇我们可知__图像的属性 链接&#xff1a;《我在Vscode学OpenCV 处理图像》 属性— API 形状 img.shape 图像大小 img.size 数据类型 img.dtype  shape&#xff1a;如果是彩色图像&#xff0c;则返回包含行数、列数、通道数的数组&#xff1b;如果是二值图像或者灰度…

基于单片机的智能扫地机设计

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、方案设计二、电路设计与理论分析2.1 硬件设计总体概要2.2 电源电路设计总控制电路的设计总电路设计 三、PCB板的设计与制作四、 结论五、 文章目录 概要 本文主要设计一个简单的智能扫地机。该扫地机的核心控…

BUUCTF ningen 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 人类的科学日益发展&#xff0c;对自然的研究依然无法满足&#xff0c;传闻日本科学家秋明重组了基因序列&#xff0c;造出了名为ningen的超自然生物。某天特工小明偶然截获了日本与俄罗斯的秘密通信&#xff0c;文…

【实战Flask API项目指南】之一 概述

实战Flask API项目指南之 概述 本系列文章将带你深入探索实战Flask API项目指南&#xff0c;通过跟随小菜的学习之旅&#xff0c;你将逐步掌握Flask在实际项目中的应用。让我们一起踏上这个精彩的学习之旅吧&#xff01; 前言 小菜是一个Python编程爱好者&#xff0c;他目前…

Angular组件生命周期详解

当 Angular 实例化组件类 并渲染组件视图及其子视图时&#xff0c;组件实例的生命周期就开始了。生命周期一直伴随着变更检测&#xff0c;Angular 会检查数据绑定属性何时发生变化&#xff0c;并按需更新视图和组件实例。当 Angular 销毁组件实例并从 DOM 中移除它渲染的模板时…

Proteus仿真--基于51单片机的按键选播电子音乐(仿真文件+程序)

本文主要介绍基于51单片机的按键选播电子音乐&#xff08;完整仿真源文件及代码见文末链接&#xff09; 本设计中包括一个按键和一个喇叭&#xff0c;点击按键可以切换选择播放不同的音乐 仿真图如下 其中点击按键K1进行音乐选播选择切换 仿真运行视频 Proteus仿真--基于5…

C++二分查找算法的应用:将数据流变为多个不相交区间

本文涉及的基础知识点 二分查找 题目 给你一个由非负整数 a1, a2, …, an 组成的数据流输入&#xff0c;请你将到目前为止看到的数字总结为不相交的区间列表。 实现 SummaryRanges 类&#xff1a; SummaryRanges() 使用一个空数据流初始化对象。 void addNum(int val) 向数据…