GNU Makefile--基本变量与规则执行

调试工具 phell

为了方便调试GNU Make的两种基本变量与规则的执行操作,笔者用C语言编写了简单的调试工具,phell(Phony Shell),不带参数的执行会输出其支持的一些选项。其主要功能是将命令行参数以及其他相关的信息输出:

yejq@UNIX:~/Downloads/phell$ phell
Usage: phell [options] COMMAND ARGUMENTS LIST
    -e         : toggle stderr option (default NO)
    -i         : dump process information
    -o OUTFILE : set output file to OUTFILE
    -p ENVNAME : print environment variable ENVNAME
    -s         : toggle stdout option (default YES)
    -x EXITVAL : set exit status for process to EXITVAL

该工具的源码可以在笔者的下载区域下载。

延迟变量和立即时变量

在GNU Make官方文档中,主要讲述了两种类型的变量:deferred variablesimmediate variables,由于缺乏文献参考,笔者将二者称为“延迟变量”、“立即变量”。二者的区别主要在于在赋值(或追加赋值)时会不会立即展开(Variable Expansion)。延迟变量主要由等于号(=)定义,立即变量则于冒号和等于号(:=)定义:

.PHONY: all clean showvar0 showvar1

var0   = $(shell phell -e -i define variable-0)
var1  := $(shell phell -e -i define variable one)

all:
	@echo "var0: $(var0)"
	@echo "var1: $(var1)"

showvar0:
	@echo "var0: $(var0)"

showvar1:
	@echo "var1: $(var1)"

clean: all
	@echo "VAR0: $(var0)"
	@echo "VAR1: $(var1)"

上面的简单示例脚本演示了两种类型变量的区别。执行make all会得到以下信息:

****** phell PID: 4037, args: 5
	-e -i define variable one

****** phell PID: 4038, args: 4
	-e -i define variable-0

var0: -e -i define variable-0
var1: -e -i define variable one

由输出结果可以确定,以:=定义的立即变量会对赋值内容立即扩展。虽然先定义了var0变量,但由于是延迟变量,其扩展在var1变量的扩展之后。执行make showvar1可以得到以下结果:

****** phell PID: 4278, args: 5
	-e -i define variable one

var1: -e -i define variable one

该结果不包含变量var0相关的信息。也就是说,延迟变量虽然定义了,但未引用就不会被扩展。接下来执行make showvar0可以得到:

****** phell PID: 4289, args: 5
	-e -i define variable one

****** phell PID: 4290, args: 4
	-e -i define variable-0

var0: -e -i define variable-0

虽然变量var1未被引用,但也被扩展了,这是立即变量的特性。立即变量是冲动的,盲目的,不能做到静观其变;当被(追加)赋值时就廹不及待地进行扩展。不过延迟变量虽然相对沉稳,但缺点也比较明显,那就是每次引用都会扩展。执行make clean可以看到,phell为了变量var0的两次引用,被执行了两次:

****** phell PID: 4393, args: 5
	-e -i define variable one

****** phell PID: 4394, args: 4
	-e -i define variable-0

var0: -e -i define variable-0
var1: -e -i define variable one
****** phell PID: 4397, args: 4
	-e -i define variable-0

VAR0: -e -i define variable-0
VAR1: -e -i define variable one

立即变量与延迟变量的混合变量类型

新版本的GNU Make引入了以!=定义的变量,但该变量在追加赋值时,会遵循延迟变量的扩展机制。下面是简单的示例:

.PHONY: all clean

var0 := $(shell phell -e define var0)
var1 != phell -e welcome var1
var2 := $(shell phell -e define var2)
var1 += $(shell phell -e aboard)
var2 += $(shell phell -e two)

all:
	@echo "var1: ${var1}"

clean:
	@echo "var2: ${var2}"

执行make all可以得到以下结果,并由此确认var1在赋值时会立即展开。但追加的信息在two之后,由此可确定其赋值是遵循延迟变量的扩展机制了:

	-e define var0
	-e welcome var1
	-e define var2
	-e two
	-e aboard

var1: -e welcome var1 -e aboard

为了进一步确认这一点,可以执行make clean,在未引用var1变量的情况下,对其追加的赋值不会被扩展,因而没有关于aboard的信息:

	-e define var0
	-e welcome var1
	-e define var2
	-e two

var2: -e define var2 -e two

查看变量的内容

为了进一步理解GNU Make对变量的处理,可以借助value函数:

.PHONY: all
var0  = $(shell phell define variable-0)
var1 := $(shell phell define variable one)

$(info $(value var0))
$(info $(value var1))

all:
	@echo "------------------"
	@echo "var0: ${var0}"
	@echo "var1: ${var1}"

可以得到以下结果:

$(shell phell define variable-0)
define variable one
------------------
var0: define variable-0
var1: define variable one

非纯粹的脚本语言

Makefile是一种混合的脚本语言,主要是由Makefile语法及Shell脚本语言组成。认识到这一点非常重要,编写精致、优秀的编译、构建脚本,要求我们首先熟悉掌握Shell脚本。相似的说法在GNU Make官方文档中可以得到印证:

5.1 Recipe Syntax
=================

Makefiles have the unusual property that there are really two distinct
syntaxes in one file.  Most of the makefile uses 'make' syntax (*note
Writing Makefiles: Makefiles.).  However, recipes are meant to be
interpreted by the shell and so they are written using shell syntax.
The 'make' program does not try to understand shell syntax: it performs
only a very few specific translations on the content of the recipe
before handing it to the shell.

可以通过定义SHELL(或MKSHELL)来指定新的SHELL。下面就将其替换为phell,以了解make工具如何执行shell脚本的:

.PHONY: all

SHELL = phell
# .SHELLFLAGS = -i

all:
	@Hello World -e

在不定义.SHELLFLAGS变量的情况下,结果如为:

-c Hello World -e

当定义.SHELLFLAGS变量为**-i**时,可以得到结果:

****** phell PID: 5801, args: 2
-i Hello World -e

注意到上面的信息,phell只有两个参数(未计入第零个参数)。两种结果综合一下,可以确定GNU Make是以:

/bin/sh -c "RECIPE LINE COMMANDS"

来调用shell解析器的。

你可能感兴趣的:(杂谈,makefile)