如何强制make工具始终执行指定目标?
在Makefile里,默认情况下make会检查目标文件和依赖文件的时间戳,只有当依赖更新时才会重新构建目标。但如果我们需要某个目标每次执行make都必须运行(比如动态生成的封面、clean清理操作这类),有两种常用的可靠方法,我结合你提供的Asciidoc转PDF的Makefile场景来详细说:
方法一:使用.PHONY伪目标标记
这是最标准、推荐的做法。.PHONY的作用就是告诉make:“这个目标不是一个实际的文件,别去检查它的时间戳,直接执行对应的规则”。
比如你提供的Makefile里,已经把需要强制执行的目标都加入了.PHONY列表:
.PHONY: all html pdf clean $(COVER)
这里的$(COVER)就是你需要每次构建都重新生成的SVG封面——毕竟你要注入最新的版本号(从git HEAD获取)、年份,还要根据标题长度调整元素,不可能依赖旧的封面文件。
对应的封面生成规则(补充完整后)大概是这样:
$(COVER): $(COVER_TEMPLATE) # 替换模板里的占位符,生成新封面 sed -e 's/{{REV}}/$(REV)/g' \ -e 's/{{YEAR}}/$(shell date +%Y)/g' \ -e 's/{{APPNAME}}/$(APPNAME)/g' \ $(COVER_TEMPLATE) > $(COVER)
因为$(COVER)被标记为.PHONY,不管当前目录下有没有cover.svg,也不管它比模板新还是旧,make每次都会执行上面的sed命令,确保封面内容是最新的。
另外像clean、all这类不需要生成文件的操作,也必须标记为.PHONY——不然如果刚好有个叫clean的文件,make就会误以为目标已经完成,不会执行清理命令了。
方法二:依赖一个“永远更新”的FORCE目标
如果你不想用.PHONY,也可以定义一个空的FORCE目标,让需要强制执行的目标依赖它:
FORCE: $(COVER): $(COVER_TEMPLATE) FORCE # 生成封面的命令
因为FORCE没有任何依赖和规则,make会认为它“永远需要更新”,所以依赖它的$(COVER)也会每次都重新构建。不过这种方法不如.PHONY直观,而且如果不小心创建了一个叫FORCE的文件,就会失效,所以还是推荐用.PHONY。
完整的示例Makefile(补充clean规则)
SHELL := /bin/bash .DEFAULT_GOAL := all # document revision REV := "0.1.$(or $(shell git rev-parse HEAD), 0)" # source directory SRCDIR := ${CURDIR}/src # theme directory THEMEDIR := ../theme # images directory IMGDIR := images # source files ADOC_FILES := $(wildcard $(SRCDIR)/*.adoc) # document cover COVER_TEMPLATE := $(SRCDIR)/$(IMGDIR)/cover-template.svg COVER := $(SRCDIR)/$(IMGDIR)/cover.svg # output directories OUTDIR := ${CURDIR}/build HTML_OUTDIR := $(OUTDIR)/html PDF_OUTDIR := $(OUTDIR)/pdf # output files BASENAME := $(shell basename `pwd`)-$(REV) HTML_FILE := $(HTML_OUTDIR)/$(BASENAME).html PDF_FILE := $(PDF_OUTDIR)/$(BASENAME).pdf # custom attributes APPNAME=MyApp .PHONY: all html pdf clean $(COVER) all: html pdf html: $(HTML_FILE) pdf: $(PDF_FILE) clean: rm -rf $(OUTDIR) rm -f $(COVER) $(COVER): $(COVER_TEMPLATE) # 注入版本、年份等信息到SVG封面模板 sed -e 's/{{REV}}/$(REV)/g' \ -e 's/{{YEAR}}/$(shell date +%Y)/g' \ -e 's/{{APPNAME}}/$(APPNAME)/g' \ $(COVER_TEMPLATE) > $(COVER)
总结一下:
- 对于不需要生成文件的操作(如clean、all),直接加入
.PHONY - 对于需要生成文件但必须每次重建的目标(如动态封面),把该文件目标加入
.PHONY列表 .PHONY是官方推荐的方式,避免了文件冲突的风险
备注:内容来源于stack exchange,提问作者j3d

