首先说一句,本文适合对Makefile了解的同学阅读。
不过看了这句可能就会有同学开始批评我我,Makefile难道不就是随便调用shell命令吗?
是的,一般介绍Makefile的文章都是这样事的:
target ... : prerequisites ...
command
...
...
可是实际上很多Makefile都是这样事的,下面代码取自https://github.com/kata-containers/runtime/blob/master/golang.mk:
golang_version_min_fields=$(subst ., ,$(golang_version_min))
golang_version_min_major=$(word 1,$(golang_version_min_fields))
golang_version_min_minor=$(word 2,$(golang_version_min_fields))
请同学们尝试从上面这个文件找一下target让我看看,另外请到非命令区调用个shell或者打印一个我看看。
为哈会这样呢?在讲正题前先讲点乱七八糟的,个人感觉Makefile最开始并没想支持太多动态的功能,什么多环境体系结构的支持,动态代码生成等等,都是想用autoconf那套来解决的。
可是autoconf那套东西实在是比较复杂,语法又是不同的一套,不同的版本还有不兼容的问题。开始用着还凑合,可是用autoconf的项目很多历史都超长,再加上新人不想努力学习autoconf,很多项目的开发者最后干脆都是直接手改configure,毕竟写shell可比写autoconf省事。弄的到头来autoconf的被新项目使用的很少,就更少有人学习autoconf。
而Makefile这种短平快的设计思路,让人看明白最开始贴的那个例子就能快速上手,直接用Makefile来维护项目就成了很多人的选择。而随着项目的发展,这些Makefile就会变得越来越复杂。很多动态的支持就要靠着非命令区简陋的变量定义和条件判断来支持,那里的语法有点像shell又不完全一样十分令人痛苦。
估计其中最大的痛苦就是不能在非命令区方便的调用shell和打印。
首先介绍下非命令区立即调用shell,网上有些教程已经介绍了一个方法:
test=$(shell pwd)
target:
echo ${test}
但是这并不能是立即调用这个shell命令,因为Makefile基本上是target驱动的执行方式,如果target这个目标没有被访问到,则这条命令并不会执行,也就是说这个pwd的shell命令还是在target的命令区才被调用到的。
像上面提到的golang.mk这样的文件,用起来就极其不方便了。
解决的方法就是在条件判断参数里访问shell或者其相关变量,例如前面介绍的不能马上运行的shell命令改为:
test=$(shell pwd)
ifeq (,$(test))
endif
或者直接写成:
ifeq (,$(shell pwd))
endif
我估计爱动手的同学马上会问我为什么放在Makefile里并没看到任何输出?
这是因为pwd的输出被放入变量$test里了,确切的说是make执行的时候,ifeq这个判断需要$test的值的时候,pwd的输出被放入$test里了。
而ifeq的判断需要$test的值也促成了pwd被立刻执行,而不需要等到命令区访问这个变量才被执行了。
而如果真的想马上让输出在Makefile执行的时候就被看到,可以把标准输出还有标准错误输出导入到当前tty,例如:
ifeq (,$(shell pwd > $(shell tty)))
endif
当然还有标准出错,另外每次都调用tty没效率,改成:
ct=$(shell tty)
ifeq (,$(shell pwd > $(ct) 2> $(ct)))
endif
注意输出导出到标准输出就不能再作为变量的值获取到了,这么输出简单直观,比较适合调试。
如果要对执行命令的返回值进行判断和处理,可以写成:
test=$(shell touch /root/1 2> /dev/null > /dev/null; echo $$?)
ifneq (0,$(test))
#touch comand failed, do something.
endif
这样$test就保存了执行“touch /root/1”的返回值,可以很方便的对其进行判断。
这个写法的缺点是为了防止对$?的影响,标准输出和出错丢掉了,实际使用如果需要可以导出到一个文件里,再把文件cat出来对输出进行处理。
写到这里,非命令区的输出方法有些同学已经可以推测出来了:
ct=$(shell tty)
ifeq (,$(shell echo "Hello world!" > $(ct)))
endif
最后总结下,整体来说这是个有点hack的手法,不过用来调试什么的还是有点用处的。