Linux Makefile变量详解

前言

我们是地球人。曾经为复杂的 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 prerequisitecc -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 = Worldall:@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 = Makeall:@echo ${foo} # Prints Hello World

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

CFLAGS = $(CFLAGS) -I.

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

立即分配

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

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

bar := World
foo :::= Hello $(bar)bar = Makeall:@echo ${foo} # Prints Hello World

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

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

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

two = twoall:@echo ${OUT} # onetwo

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

条件赋值

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

foo = Worldfoo ?= Make # foo will not change
bar ?= Make # bar will changeall:@echo Hello ${foo}@echo Hello ${bar}

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

foo ?= Make# is equivalent toifeq ($(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 = Maketarget-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 = Boball:@echo ${USER}

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

命令行参数

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

override FOO = Hello
BAR = Worldall:@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 += Worldbar := 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 helloone:@touch onetwo:@touch twoclean:@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.mzph.cn/news/128304.shtml

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

相关文章

【Proteus仿真】【51单片机】贪吃蛇游戏

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用8*8LED点阵、按键模块等。 主要功能&#xff1a; 系统运行后&#xff0c;可操作4个按键控制小蛇方向。 二、软件设计 /* 作者&#xff1a;嗨小易…

Keil uv5 MDK使用教程

目录 前言一、开发环境搭建1.1 Keil的安装1.2 其他工具安装1.3 注意事项 二、Keil基本使用2.1 新建工程模板2.1.1 基于固件库&#xff08;先复制文件夹&#xff0c;后添加文件&#xff09;2.1.2 基于寄存器2.1.3 基于HAL库 2.2 下载与调试2.3 工程目录下简介2.4 MDK使用技巧 前…

JavaScript

文章目录 1、JavaScript 的历史1.1 JavaScript 的历史1.2 JavaScript与ECMAScript的关系1.3 JavaScript与Java的关系1.4 JavaScript的版本 2、JS的引入方式3、ECMAScript基本语法3.1 变量3.2 注释3.2 语句分隔符 4、ECMAScript 基本数据类型4.1 数字类型4.2 字符串4.2.1 创建4.…

【计算机网络】运输层

概述运输层服务 运输层协议为运行在不同主机上的应用程序提供了逻辑通信功能。 运输层协议是在端系统中而不是在路由器中实现的。 运输层和网络层的关系&#xff1a; 网络层提供主机之间的逻辑通信&#xff0c;而运输层为**运行在不同主机上的应用程序&#xff08;进程&#…

python爬虫利用代理IP分析大数据

目录 前言 一、什么是代理IP&#xff1f; 二、为什么需要使用代理IP&#xff1f; 1.突破访问限制 2.提高访问速度 3.保护隐私 三、代理IP的分类 1.高匿代理IP 2.普通代理IP 3.透明代理IP 四、如何获取代理IP&#xff1f; 1.免费代理IP网站 2.付费代理IP服务商 五…

前端基础之CSS

目录 一、CSS介绍 CSS语法 CSS注释 CSS的几种引入方式 二、CSS选择器 基本选择器 组合选择器 属性选择器 分组和嵌套选择器 伪类选择器 伪元素选择器 选择器的优先级 三、CSS属性相关 宽和高 字体属性 文字属性 背景属性 边框 border-radius display属性 …

常见接口测试面试题

1、按你的理解&#xff0c;软件接口是什么&#xff1f; 答&#xff1a; 就是指程序中具体负责在不同模块之间传输或接受数据的并做处理的类或者函数。 2、HTTP和HTTPS协议区别&#xff1f; 答&#xff1a; https协议需要到CA&#xff08;Certificate Authority&#xff0c;证书…

物联网整体框架有哪些层面?

物联网是当前非常火热的话题&#xff0c;各个行业对物联网的关注和投入力度也很大&#xff0c;一些互联网巨头都在紧锣密鼓的布局物联网产业&#xff0c;抢占市场先机。 物联网的整体构架大致可以分为以下四个层面&#xff1a; 1.感知识别层 感知层是物联网整体架构的基础&…

HTML标题、段落、文本格式化

HTML标题&#xff1a; 在HTML文档中&#xff0c;标题是很重要的。标题是通过<h1> - <h6标签进行定义的&#xff0c;<h1> 定义最大的标题&#xff1b;<h6>定义最小的标题。 <hr> 标签在HTML页面中用于创建水平线&#xff0c;hr元素可用于分隔内容。…

【Linux】Nginx安装使用负载均衡及动静分离(前后端项目部署),前端项目打包

一、Nginx导言 1、引言 Nginx 是一款高性能的 Web 服务器和反向代理服务器&#xff0c;也可以充当负载均衡器、HTTP 缓存和安全防护设备。它的特点是内存占用小、稳定性高、并发性强、易于扩展&#xff0c;因此在互联网领域得到了广泛的使用。 总结出以下三点: 负载均衡&#x…

CN考研真题知识点二轮归纳(4)

持续更新&#xff0c;上期目录&#xff1a; CN考研真题知识点二轮归纳&#xff08;4&#xff09;https://blog.csdn.net/jsl123x/article/details/134135134?spm1001.2014.3001.5501 1.既可以扩展网段又是二层的设备 网段一般指一个计算机网络中使用同一物理层设备&#xff…

小程序如何设置自动使用物流账号发货

小程序支持自动使用物流账号发货并生成运单号。商家需要与物流公司合作&#xff0c;获取物流账号&#xff0c;支持快递物流和同城外卖配送平台。具体方法请参考公众号之前发布的文章&#xff0c;例如可以搜索“快递账号”。 导入物流账号后&#xff0c;在小程序管理员后台->…

出海营销必看:如何避免邮件被识别为垃圾邮件

对于现在的商业环境来说&#xff0c;邮件通信已经成为企业与客户、合作伙伴以及员工之间沟通和交流的重要方式。然而&#xff0c;尽管企业发送的邮件通常都是正常的、合规的&#xff0c;有时候却会被系统错误地标记为营销邮件。这个情况给企业带来了很多困扰。 如果企业的邮件…

如何使用内网穿透远程访问Linux SVN服务?

文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…

使用vue3+vite+elctron构建小项目介绍Electron进程间通信

进程间通信 (IPC) 是在 Electron 中构建功能丰富的桌面应用程序的关键部分之一。 由于主进程和渲染器进程在 Electron 的进程模型具有不同的职责&#xff0c;因此 IPC 是执行许多常见任务的唯一方法&#xff0c;例如从 UI 调用原生 API 或从原生菜单触发 Web 内容的更改。 在 …

input 调起键盘 ,键盘距离输入框底部太近

input 调起键盘 &#xff0c;键盘距离输入框底部太近 解决方法 cursorSpacing‘20’ 单位是 ‘px’ <input cursorSpacing20 type"text" v-model"replyMain" />距离底部距离 20px &#xff0c;输入框距离键盘距离是20px

centos7 配置搭建 wordpress 博客

环境配置 系统:centos7 CPU:2核 内存:4G 硬盘:40G 一、登录云服务器器 1.单击实例--实例名称 2. 选择安全组页签,单击安全组操作列的管理规则, 3.在入方向添加需要放行的端口。本教程中,在安全组入方向放行SSH默认22端口、Apache默认80端口 4.登录服务器 5.更改主…

oracle如果不适用toad或者plsql工具如何获取索引建表语句

select dbms_lob.substr(dbms_metadata.get_ddl(INDEX,INDEX_NAME,DIXON))||; from dba_indexes where ownerDIXON这个语句可以获取dixon用户的所有索引创建语句&#xff0c;sql脚本形式呈现 点开一个语句查看 如果不使用dbms_lob.substr这个函数最后得到是一个clob selec…

USART HMI串口屏+单片机通讯上手体验

USART HMI串口屏单片机通讯上手体验 &#x1f516;本文采用淘晶驰4.3寸IPS串口屏实物验证&#xff0c;HMI串口屏经简单配置即可快速实现&#xff0c;串口通讯效果。串口屏上手简单&#xff0c;有独立的开发套件&#xff0c;容易上手&#xff0c;驱动显示和功能代码独立。本文仅…

2021上半年下午网络工程师试题

2021上半年下午网络工程师试题 试题一(共20分) 阅读以下说明&#xff0c;回答问题1至问题4&#xff0c;将解答填入答题纸对应的解答栏内。 【说明】 某企业网络拓扑图如图1-1所示。该网络可以实现的网络功能有: 1.汇聚层交换机A与交换机B采用VRRP技术组网&#xff1b; 2.…