用 Tcl 作为 Makefile 中命令的解释器

Makefile 默认是用 /bin/sh 作为命令解释器的。

需要承认的是,对于稍微复杂一点的逻辑,Shell命令用起来并不是很顺手。

GNU Make 从 Version 3.64 (21 Apr 1993) 开始支持通过SHELL变量来指定命令解释器。这给了我们用 Tcl 写 Makefile 的可能。

用Tcl作为Makefile命令解释器

SHELL = /usr/bin/tclsh

tets-tcl:
        puts "Hello Tcl from Make"

通过SHELL变量指定TCL解释器。

运行上面这个最简单的例子,会发现Tcl命令并没有实际执行,并且Tcl解释器不会退出。

究其原因,实际上是因为Tcl收到的命令等效于

/usr/bin/tclsh -c 'puts "Hello Tcl from Make"'

根据tclsh程序的实现,由于第一个参数-字符开头,tclsh 解释器认为没有要执行的脚本,而是把所有参数作为$::argv的内容,等待从 stdin 读取Tcl命令。

解决办法自然是指定一个Tcl脚本。

可以取巧的是,tclsh默认会读取~/.tclshrc文件作为启动脚本。因而我们可以在其中写入以下命令。

# File: ~/.tclshrc

if {[lindex $::argv 0] eq "-c"} {
  eval [lindex $::argv 1]
  exit
}

.ONESHELL

上面的方案有个问题,就是每一行Tcl命令都是在单独的Tcl解释器中执行的,因而无法写出需要多行命令的复杂逻辑。

GNU Make 版本 Version 3.82 (28 Jul 2010) 开始提供 .ONESHELL 这个特殊目标。因此,我们改成下面这样就可以了。

SHELL = /usr/bin/tclsh
.ONESHELL:

tets-tcl:
        set hello 123
        puts "hello = $$hello"

需要注意的是,这里的$字符仍然需要写两个,以避免被当作Makefile的变量处理。

ONESHELL 的情况下,用于不显示当前命令的特殊字符@-只需要加在第一行命令的前面。

.RECIPEPREFIX

Makefile默认用Tab字符(\t)作为命令的前缀的。我们也许希望让用Tcl语言写的Makefile和默认的有点区分。

可以借助.RECIPEPREFIX这个变量。类似于这样:


SHELL = /usr/bin/tclsh
.RECIPEPREFIX = %
.ONESHELL:

tets-tcl:
% puts "Hello Tcl from Make"
% puts "Hello Again"

.SHELLFLAGS

前面提到的额外传递给/usr/bin/tclsh的参数-c来自Makefile的追加。

从 GNU Make 版本 Version 3.82 (28 Jul 2010) 开始,可以通过 .SHELLFLAGS 变量进行修改。

比如我们可以写成

.SHELLFLAGS = -f tclmake.tcl

这使得我们可以根据项目的不同定制用于Makefile里Tcl命令处理模板。比如,把命令发送到某个服务端程序上执行。

附记

GNU Makefile 的版本变迁历史在源文件里的 NEWS 文件中获得。

GNU Make 官方网站:https://www.gnu.org/software/make/