make 的静态模式和条件

Static patterns and conditionals with make

我对 GNU Make 4.2.1 有疑问。好像有一些互动 static pattern rules 条件语法之间 功能我不太明白。

上下文:我有一个由一组 Markdown 文件记录的项目,并且 我想将这些文件渲染到 HTML 以检查它们 本地。目录结构最终应如下所示:

some_project/
├── README.md       # entry page of documentation
├── doc/            # extra docs
│   ├── foo.md
│   ├── bar.md
│   └── ...         # and some more
└── doc_html/       # HTML rendering of the docs
    ├── Makefile    # the Makefile I am trying to write
    ├── index.html  # rendered from README.md
    ├── foo.html    # ............. doc/foo.md
    ├── bar.html    # ............. doc/bar.md
    └── ...         # etc.

如果没有 index.html 的特殊情况,我可以这样写:

%.html: ../doc/%.md
    some list of commands $< $@

问题是index.html的前提(即../README.md) 与模式不匹配。我想处理这个特殊情况 无需重复整个命令列表。这就是我所拥有的 到目前为止:

DOC_PAGES = $(wildcard ../doc/*.md)
TARGETS = index.html $(patsubst %.md,%.html,$(notdir $(DOC_PAGES)))

# Function to find the source for page $(1)
source = $(if $(findstring index.html,$(1)), \
    ../README.md, \
    $(patsubst %.html,../doc/%.md,$(1)) \
)

all: $(TARGETS)

$(TARGETS): %.html: $(call source,%.html)
    @echo some list of commands $< $@

# Check the TARGETS variable and the `source' function
test:
    @echo TARGETS = $(TARGETS)
    @echo "source(index.html)" = $(call source,index.html)
    @echo "source(foo.html)" = $(call source,foo.html)

我的 source 功能似乎有效:

$ make test
TARGETS = index.html bar.html foo.html
source(index.html) = ../README.md
source(foo.html) = ../doc/foo.md

但是,它在静态规则中表现不正常

$ make
make: *** No rule to make target '../doc/index.md', needed by 'index.html'.  Stop.

请注意,如果我从 $(TARGETS) 中删除 index.html,该规则会起作用。

知道我做错了什么吗?

这一行:

$(TARGETS): %.html: $(call source,%.html)

无法工作,因为您正在使用文字字符串 %.html 的参数扩展 source 宏。您不能在先决条件列表中的宏中使用模式或自动变量:在解析或扩展模式之前先扩展宏

但是,在我看来,您使这种方式变得比需要的更复杂。如果你的大部分目标都是以一种方式构建的,但有一些是用不同的方式构建的,那么只需为 "most" 创建一个模式规则并为 "some":

编写明确的规则
%.html: ../doc/%.md
        some list of commands $< $@

index.html : ../README.md
        commands to build index.html

如果命令集相同并且您不想重复它们,请将它们放在一个变量中:

create_html = some list of commands $< $@

%.html: ../doc/%.md
        $(create_html)

index.html : ../README.md
        $(create_html)

(如果要在脚本中包含 $<$@,请务必使用 = 而不是 := 创建变量)。

ETA 您询问了为什么事情似乎有效:当 make 扩展它时,它将替换文字字符串 %.html。您可以通过添加如下所示的 $(info...) 调用来向自己证明这一点:

source = $(info 1=$(1)) $(if $(findstring index.html,$(1)), \
    ../README.md, \
    $(patsubst %.html,../doc/%.md,$(1)) \
)

你会看到它会打印(只打印一次,因为规则只展开一次)1=%.html.

接下来会发生什么?那么这意味着您的宏扩展为:

$(if $(findstring index.html,%.html), ../README.md, $(patsubst %.html,../doc/%.md,%.html))

(再次使用文字字符串 %.html)。 findstring 始终 returns 为空,因为在字符串 %.html 中找不到 index.html,因此您展开 else 子句:

$(patsubst %.html,../doc/%.md,%.html)

显然 %.html%.html 与词干 % 匹配,因此进行了替换 returns ../doc/%.md。所以毕竟你的规则是这样的:

$(TARGETS): %.html: ../doc/%.md
        @echo some list of commands $< $@

完全与您之前的简单模式规则相同。

这似乎是一个解决方案:

DOC_PAGES := $(wildcard ../doc/*.md)
TARGETS := index.html $(patsubst %.md,%.html,$(notdir $(DOC_PAGES)))

all: $(TARGETS)

.INTERMEDIATE: ../doc/index.md
../doc/index.md: ../README.md
    cp $< $@

%.html: ../doc/%.md
    @echo some list of commands $< $@

test:
    @echo $(TARGETS)

输出:

$ make
cp ../README.md ../doc/index.md
some list of commands ../doc/index.md index.html
some list of commands ../doc/bar.md bar.html
some list of commands ../doc/foo.md foo.html
rm ../doc/index.md

index.html通过pattern规则查找../doc/index.md,导致配方复制../README.md.

.INTERMEDIATE 特殊目标的先决条件在 make 完成时被移除。可选。