Makefile简单用法备忘录
去年(2017年),我至少花了8个月的时间研究Hyperledger Fabric和BaaS平台的设计,深有体会,Fabric的难用和复杂。我个人认为要消除构建Fabric运行环境过程中的各种问题,一定要从Fabric的Makefile入手,这样可以极大地帮助工程师理解Fabric的各种组件的生成原理,进而加深理解Fabric的体系结构。比如说,我当时就是通过分析Makefile,剥离出了一些简化流程,可以以进程的模式启动Fabric,而不是运行了一堆Docker容器,这就让我能够集中精力做区块链的研究,而不是跑偏题到Docker相关的领域中。然而Makefile的语法比较难记忆,我在2007年做C语言开发的时候就经常用到,但是等到2017年的时候,就基本全忘了。因此有必要总结一点记录在这里,若以后再忘记了,也可以通过阅读本文快速回顾起来。后续,我可能还会给出Fabric项目的Makefile的完整分析。
Makefile/make的核心设计思想:依据依赖对象,执行某种动作,生成目标对象;依赖对象可以是多个,这时候以集合形式呈现,依赖集也可以为空集。目标与依赖集位于同一行,以冒号为分割,命令动作新起一行,以Tab符开头,如下:
目标对象1:依赖对象集1
命令动作1;
命令动作2;
命令动作n;
目标对象2:依赖对象集2
命令动作1;
命令动作2;
命令动作n;
若依赖集中的某个对象比目标对象要新或者目标对象还没有生成、不存在,那么就触发命令动作的执行。也可以用".PHONY"修饰目标对象,让make伪造、假想出目标对象,而不是去检查具体的目标文件及其时间戳,无论是否真实存在和伪目标同名的文件,当make进程正在构建的其他目标依赖到伪目标时,或者直接执行make <伪目标名>时,总会触发执行对应的命令动作。命令动作是shell可执行的任何命令,make启动shell,shell再启动命令,最常用的命令是编译、连接、构建项目相关的。在软件开发项目中,源代码经常被改动,源代码文件比之前编译好的二进制目标文件要新,因此用make启动编译过程是极其普遍的。软件项目的最高层Makefile一般放置于项目源码的顶层目录中,Makefile中也可以引用其他Makefile。进入Makefile所在的目录中,在shell环境下执行make <目标名>,命令动作就会被触发。make语法中有变量、函数、条件判断,和通用的编程语言在形式上非常类似,因此可以认为make是一种面向特殊目的编程语言, 而Makefile就是用这个语言编写出的脚本程序。
Makefile/make的缺点:虽然make的核心原理非常简单,但是make采用了大量的特殊字符和语法含义约定,比shell、awk、sed等工具的语法要复杂,一个大型项目的Makefile对于初学者来说可能有点像天书。即使对于经验丰富的IT工作者,虽然学习和研究过make,若不经常使用,过一段时间可能就遗忘了,难于记忆。个人认为没有必要深入研究make的诸多语法细节,因为完整掌握一个工具的使用方法并不能提升一个人的灵感与创造力。在大致掌握软件工具的核心思想与基本用法后,能够辅助我们编写出实用的程序,满足实际项目需求,就足够了。毕竟最终能够给社会提供服务的是我们创作的程序,而不是我们掌握的工具技能。创造比学习更有乐趣,也更有价值。
常见的的符号约定:
$^:依赖集对象列表
$<:依赖集对象列表中的第一个
$@:指代目标本身,$(@D)指代目标的路径名部分,$(@F)指代目标的文件名部分
$(wildcard $^):$^中实际存在于本地文件系统上的文件列表
常用等号赋值语句:
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被被定义或赋值,就定义并赋值
+= 当前值后面追加新值
需要特别注意的是=与:=的区别,基本等号在赋值的时候,会等待引用的变量最终值,而冒号等号在执行时,取当前值,当前值是和变量在Makefile中的位置是有关系的。举个例子:
all:
@echo b = $(b) d = $(d)
a = 123
b = $(a)
a = 456
c := 123
d := $(c)
c := 456
执行make all,输出结果:
b = 456 d = 123
常用字符替换函数
$(patsubst pattern , replacement , text)
依次替换text中和patter匹配的字段为replacement,示例:
IMAGES=peer order
DUMMY=.dummy
$(patsubst %,build/image/%/$(DUMMY), $(IMAGES))
调用patsubst返回:build/image/peer/.dummy build/image/orderer/.dummy
$(subst from, to, text),将字符串text中的子串from变为to。示例:
comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
当前bar的值为a,b,c
显示或关闭显示正在执行的动作命令
若动作命令以@开头,表示make在执行到该命令时,不要打印输出命令本身,否则make执行一个命令时,会先打印出正在执行的命令字符串。
可以用make <目标名> 启动构建某个目标的过程,若想查看这个过程的具体步骤或对Makefile做调试,可以执行make -n <目标名>,或者用--just-print或者--dry-run选项,这时make会推导目标构建过程,并打印出对应的动作命令。输出结果仅供调试分析,和真正执行还是会有区别的。因为真正执行的时候,某个动作命令可能会异常中止退出,有些语句可能会判断某个命令返回值进入不同的条件分支等等。