Makefile详解

1. Makfile总述

1.1. make如何解析makefile文件

GUN make 的执行过程分为两个阶段。

  • 第一阶段:读取所有的 makefile 文件(包括 MAKIFILES 变量指定的、指示符 include 指定的、以及命令行选项 -f(--file) 指定的 makefile 文件),内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖之间的依赖关系结构链表。
  • 第二阶段:根据第一阶段已经建立的依赖关系结构链表决定哪些目标需要更新,并使用对应的规则来重建这些目标。

在 make 执行的第一阶段中如果变量和函数被展开,那么称此展开是“立即”的,此时所有的变量和函数被展开在需要构建的结构链表的对应规则中(此规则在建立链表是需要使用)。其他的展开称之为“延后”的。这些变量和函数不会被“立即”展开,而是直到后续某些规则需要使用时或者在 make 处理的第二阶段它们才会被展开。

1.2. Make变量和函数展开时机

变量和函数的展开时机在变量取值,条件语句和规则的定义中的如下:

  1. 变量取值的解析规则如下:

    IMMEDIATE = DEFERRED
    IMMEDIATE ?= DEFERRED
    IMMEDIATE := IMMEDIATE
    IMMEDIATE += DEFERRED or IMMEDIATE
    define IMMEDIATE 
        DEFERRED
    Endef
    
  2. 条件语句的展开是“立即”的。其中包括:ifdefifeqifndefifneq 所确定的所有分支命令。

  3. 规则的定义按照如下的模式展开:

    IMMEDIATE : IMMEDIATE ; DEFERRED   
        DEFERRED
    

1.3. 小结

以上内容介绍的 Makefile 的解析流程,需要有一定的基础。可以先查看后续章节对Makefile的组成元素有一定了解,再返回来查看此章节。本文大部分内容摘自《Makefile中文手册》,如需详细了解请查看原著。后续章节将介绍组成 Makefile 的规则变量条件判断函数注释

2. Makefile的规则

Makefile 中,规则描述了在何种情况下使用什么命令来重建一个特定的文件。规则中的命令执行的条件是:1.目标文件不存在2.存在依赖文件比目标文件新。这是 Makefile 的核心思想。

2.1. 规则语法

通常规则的语法格式如下:

TARGETS : PREREQUISITES
	COMMAND
	... 

或者

TARGETS : PREREQUISITES; COMMAND
	COMMAND
	... 

我们需要注意几点:

  • 命令行必须以 [tab] 字符开始,命令行运行在系统shell之上。
  • 目标和依赖可以是文件名,也可以是标签(例如:cleanall)。
  • 目标无依赖文件时,如果目标文件存在则目标文件总为新,即命令不执行。
  • 文件名可以使用通配符,和 Bourne shell 完全相同,比如 *?[...] 等。
  • 行尾的 \ 可以延申物理行,即把几行合并为一行,方便书写和阅读。

2.2. Makefile特殊目标

2.2.1. 伪目标

通过 .PHONY 申明一个目标为伪目标,那么此目标的生成命令总是会被执行。比如:

PHONY : clean 

可以避免工作目录下存在同名文件 clean 的情况。

2.2.2. 强制目标

如果一个规则没有命令或者依赖,并且它的目标不是一个存在的文件名。在执行此规则时,目标总会被认为是最新的。这样的目标在作为一个规则的依赖时,规则中定义的命令总会被执行。比如:

clean: FORCE
	rm $(objects)
FORCE:

此处的申明方式,和把 clean 申明为伪目标效果相同。

2.2.3. 其他特殊目标

其他特殊目标如 .SUFFIXES.DEFAULT 等,请参考《Makefile中文手册》第4.9章

2.3. 多目标

一个规则可以有多个目标,规则所定义的命令对所有的目标有效。多目标规则意味着所有的目标具有相同的依赖文件。

2.4. 多规则目标

一个文件可以作为多个规则的目标(多个规则中只能有一个规则定义命令)。这种情况时,以这个文件为目标的规则的所有依赖文件将会被合并成此目标一个依赖文件列表,当其中任何一个依赖文件比目标更新(比较目标文件和依赖文件的时间戳)时,make 将会执行特定的命令来重建这个目标。

2.5. 静态模式

规则存在多个目标,并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。

TARGETS ...: TARGET-PATTERN: PREREQ-PATTERNS ...
	COMMANDS
	...

首先在目标模式和依赖模式中,一般需要包含模式字符 % 。在目标模式(TAGET-PATTERN)中 % 可以匹配目标文件的任何部分,用模式字符 % 匹配的部分替代依赖模式(PREREQ-PATTERNS)中的相应部分来产生对应目标的依赖文件。

2.6. 双冒号规则

双冒号规则和普通规则的不同体现在以下两个方面:

  • 对于一个没有依赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执行。而普通规则,命令永远不会被执行。

  • 当同一个文件作为多个双冒号规则的目标时。这些不同的规则会被独立的处理。而不是像普通规则那样合并所有的依赖到一个目标文件。

2.7. 命令

规则的命令由一些shell命令行组成,它们一条一条的执行。每一行必须以 [Tab] 字符开始。多个命令行之间可以有空行和注释行。

2.7.1. 命令回显

如果规则的命令行以字符 @ 开始,则 make 在执行这个命令时就不会回显这个将要被执行的命令。比如:@echo 开始编译 XXX 模块 ......

2.7.2. 命令的执行

对于多行命令,每一行命令将在一个独立的子 shell 进程中被执行。命令的解析使用环境变量 SHELL 所指定的那个程序,默认的程序是 /bin/sh

2.7.3. 并发执行命令

可以通过 make 的命令行选项 -j 或者 --job 来告诉 make 在同一时刻可以允许多条命令同时被执行。

2.7.5. make的递归执行

在 Makefile 中使用 make 作为一个命令来执行本身或者其它 makefile 文件。比如:

subsystem:
	$(MAKE) -C subdir
	...

2.7.6. 空命令

为了防止 make 在执行时试图为重建这个目标去查找隐含命令(包括了使用隐含规则中的命令和 .DEFAULT 指定的命令,可以定义一个空命令:

target: ;

3. Makefile中的变量

3.1. 变量的定义

Makefile 变量的定义有两种风格,主要区别在展开时机。

  1. 递归展开式变量,如果此变量定义中存在对其他变量的引用,这些被引用的变量会在它被展开的同时被展开。
  2. 直接展开式变量,变量在定义时就完成了对所引用变量和函数的展开。
IMMEDIATE = DEFERRED
IMMEDIATE ?= DEFERRED
IMMEDIATE := IMMEDIATE
IMMEDIATE += DEFERRED or IMMEDIATE
define IMMEDIATE
	DEFERRED
Endef

3.2. 变量的引用

变量的引用方式是:$(VARIABLE) 或者 ${VARIABLE}。对一个变量的引用可以在 Makefile 的任何上下文中,目标、依赖、命令、绝大多数指示符和新变量的赋值中。

变量引用的展开过程是严格的文本替换过程,就是说变量值的字符串被精确的展开在变量被引用的地方。

注意:由于 $ 符号有特殊意义,因此当需要直接使用 $ 时需要转义,需要写两个 $,即$$

3.2.1. 变量的替换引用

使用“替换引用”将其值中的后缀字符(串)使用指定的字符(字符串)替换。格式为$(VAR:A=B) 或者 ${VAR:A=B}。例如:

foo := a.o b.o c.o   
bar := $(foo:.o=.c) 

另一种替换的技术使用功能更强大的 patsubst 函数,需要包含模式字符 %,例如:

foo := a.o b.o c.o
bar := $(foo:%.o=%.c) 

3.3. override指示符

如果通过命令行定义了一个变量,那么它将替代在 Makefile 中出现的同名变量的定义。override 指示符可以避免这种作用。例如:

override VARIABLE = VALUE

3.4. 自动化变量

自动化变量的取值是根据具体所执行的规则来决定的,取决于所执行规则的目标和依赖文件名。

  • $@ 表示规则的目标文件名。
  • $% 代表静态库的一个成员名,如果目标不是静态库文件,其值为空。
  • $< 规则的第一个依赖文件名。
  • $? 所有比目标文件更新的依赖文件列表,空格分割。
  • $^ 规则的所有依赖文件列表,使用空格分隔。
  • $+ 类似“$^”,但是它保留了依赖文件中重复出现的文件。
  • $* 在模式规则和静态模式规则中,代表“茎”。

其他如 $(@D)$(@F) 等请参考 《Makefile中文手册》第10.5.3章

3.5. 系统环境变量

make 在运行时,系统中的所有环境变量对它都是可见的。在 Makefile 中,可以引用任何已定义的系统环境变量。通过 export 指示符可以将一个普通变量变为系统环境变量。

3.6. 目标指定变量

目标指定变量值只在指定它的目标的上下文中有效,对于其他的目标没有影响。语法如下:

TARGET ... : VARIABLE-ASSIGNMENT

3.7. 模式指定变量

模式指定变量定义是将一个变量值指定到所有符合此模式的目标上。语法如下:

PATTERN ... : VARIABLE-ASSIGNMENT

4. Makefile的条件判断

条件语句可以根据一个变量的值来控制 make 执行或者忽略 Makefile 的特定部分。

4.1. 条件判断的基本语法

基本语法如下:

CONDITIONAL-DIRECTIVE   
TEXT-IF-TRUE   
Endif

或者

CONDITIONAL-DIRECTIVE   
TEXT-IF-TRUE   
else   
TEXT-IF-FALSE   
Endif

4.2. 判断关键字

  • ifeq : 参数是否相等
  • ifneq : 参数是否不相等
  • ifdef : 变量是否已经定义
  • ifndef : 变量是否未定义

5. Makefile中的内嵌函数

5.1. 调用语法

GNU make 函数的调用格式类似于变量的引用,以 $ 开始表示一个引用。 语法格式如下:

$(FUNCTION ARGUMENTS)

或者:

${FUNCTION ARGUMENTS}

对于函数调用的格式有以下几点说明:

  • FUNCTION 是指make 内嵌的函数名,用户自己的函数需要通过 make 的 call 函数来间接调用。
  • ARGUMENTS 是函数的参数,参数和函数名之间使用若干个空格或者 [tab] 字符分割。如果存在多个参数时,参数之间使用逗号 , 分开。
  • 函数处理参数时,参数中如果存在对其它变量或者函数的引用,首先对这些引用进行展开得到参数的实际内容。而后才对它们进行处理。参数的展开顺序是按照参数的先后顺序来进行的。
  • 函数的参数不能出现逗号 , 和空格。在实际书写 Makefile 时,当有逗号或者空格作为函数的参数时,需要把它们赋值给一个变量,在函数的参数中引用这个变量来实现。

5.2. 内嵌函数

下面列出了所有make函数,每个函数的具体使用使用说明请参考 《Makefile中文手册》第8章

5.2.1. 文本处理函数

#文本处理函数
$(subst FROM,TO,TEXT) 
$(patsubst PATTERN,REPLACEMENT,TEXT)
$(strip STRINT)
$(findstring FIND,IN)
$(filter PATTERN…,TEXT)
$(filter-out PATTERN...,TEXT)
$(sort LIST)
$(word N,TEXT)
$(wordlist S,E,TEXT)
$(words TEXT)
$(firstword NAMES…)

5.2.2. 文件名处理函数

#文件名处理函数
$(dir NAMES…)
$(notdir NAMES…)
$(suffix NAMES…)
$(basename NAMES…)
$(addsuffix SUFFIX,NAMES…)
$(addprefix PREFIX,NAMES…)
$(join LIST1,LIST2)
$(wildcard PATTERN)

5.2.3. 其他函数

$(foreach VAR,LIST,TEXT)
$(if CONDITION,THEN-PART[,ELSE-PART])
$(call VARIABLE,PARAM,PARAM,...)
$(value VARIABLE)
$(eval VARIABLE)
$(origin VARIABLE)
$(shell VARIABLE)
$(error TEXT…)
$(warning TEXT…)
上一篇:使用VSCode和CMake构建跨平台的C/C++开发环境


下一篇:【Linux编程】Makefile 的工作流程!