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 variables
和immediate 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解析器的。