为什么在一个循环中 make 运行 一个特定规则 3 次?

Why does make run one specific rule 3 times in a loop?

我编写了一个 makefile,它采用 Word DOCX 文件并(在 tei-xslt、xslt 脚本和 saxon 的帮助下)在 TEI-XML、HTML 文件中生成表示,并且用于摄取到发布软件中的 zip 文件。不同的步骤应该是这样的:

DOCX -> TEI-XML -> HTML -> (manifest.yml) -> ZIP # (Expected)

问题是在进入 HTML 到 ZIP 规则之前,在循环中使 运行s TEI 到 HTML 规则:

DOCX -> TEI-XML -> -> -> HTML -> (manifest.yml) -> ZIP # (What happens)
                   3x

更令人惊讶的是,只有当构建过程之前已经 运行 至少一次并且所有其他文件已经存在于某个更早的状态时,才会发生这种情况。如果该文件夹仅包含 DOCX 文件,则一切正常。此外,使用 make all 或仅使用 make 没有区别。始终 运行 整个构建过程,即使没有文件更改。

make 文件如下:

SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
include ${SELF_DIR}config.mk

MANUSCRIPTFILE:=$(shell ls *.docx)
MANUSCRIPTNAME:=$(shell basename ${MANUSCRIPTFILE} .docx)
SERIES:=$(shell echo "${MANUSCRIPTNAME}" | cut -d _ -f 1)

.PHONY : all tei clean

all: manifold/${MANUSCRIPTNAME}.zip

tei: tei/${MANUSCRIPTNAME}.xml

tei/${MANUSCRIPTNAME}.xml: ${MANUSCRIPTNAME}.docx
    @echo -e "\n[BUILD] Convert Word DOCX to TEI-XML via docxtotei and melusina scripts\n"
# Make available relevant XSL stylesheets to the master styleshet
    @mkdir -p ${TRANSFORMDIR}/melusina/docx/current
# BUG hier findet er das vorhandene Dokument nicht
    @cp ${TRANSFORMDIR}/melusina/docx/specific/${SERIES}.xsl ${TRANSFORMDIR}/melusina/docx/current/series.xsl
    @if [ -e ${TRANSFORMDIR}/melusina/docx/specific/${SERIES}/${MANUSCRIPTNAME}.xsl ]; then\
        cp ${TRANSFORMDIR}/melusina/docx/specific/${SERIES}/${MANUSCRIPTNAME}.xsl ${TRANSFORMDIR}/melusina/docx/current/publication.xsl;\
    else\
        cp ${TRANSFORMDIR}/melusina/empty.xsl ${TRANSFORMDIR}/melusina/docx/current/publication.xsl;\
    fi
    @mkdir -p tei/media
    @${BINDIR}/docxtotei --profiledir=${PROFDIR} --profile=melusina $< $@
    @cp ../../assets/*.png tei/media/
    @cp -r assets/* tei/media/
    @rm -rf ${TRANSFORMDIR}/melusina/docx/current

xhtml/*.html: tei/${MANUSCRIPTNAME}.xml
    @echo -e "\n[BUILD] Convert Word TEI-XML to HTML via teitohtml and melusina scripts\n"
# Make available relevant XSL stylesheets to the master styleshet
    @mkdir -p ${TRANSFORMDIR}/melusina/html/current
    @cp ${TRANSFORMDIR}/melusina/html/specific/${SERIES}.xsl ${TRANSFORMDIR}/melusina/html/current/series.xsl
    @if [ -e ${TRANSFORMDIR}/melusina/html/specific/${SERIES}/${MANUSCRIPTNAME}.xsl ]; then\
        cp ${TRANSFORMDIR}/melusina/html/specific/${SERIES}/${MANUSCRIPTNAME}.xsl ${TRANSFORMDIR}/melusina/html/current/publication.xsl;\
    else\
        cp ${TRANSFORMDIR}/melusina/empty.xsl ${TRANSFORMDIR}/melusina/html/current/publication.xsl;\
    fi
    @mkdir -p xhtml
# generate front matter
    @java -jar ${SAXON}/saxon9he.jar -o:xhtml/front.html $< ${TRANSFORMDIR}/other/html_front_matter.xsl series=${SERIES}
# copy assets from tei folder
    @if [ -e tei/media ]; then\
        cp -r tei/media xhtml/;\
    else\
        mkdir -p xhtml/media;\
    fi
# copy stylesheets
    @cp ${TRANSFORMDIR}/css/melusina.css xhtml/
    @cp ${TRANSFORMDIR}/css/specific/${SERIES}.css xhtml/publication.css
# transform tei xml to html
    @${BINDIR}/teitohtml --profiledir=${PROFDIR} --profile=melusina $< xhtml/${MANUSCRIPTNAME}.html
    @rm -rf ${TRANSFORMDIR}/melusina/html/current

manifold/manifest.yml: tei/${MANUSCRIPTNAME}.xml
    @echo -e "\n[BUILD] Generate manifest.yml from TEI-XML via Saxon and melusina scripts\n"
    @mkdir -p manifold
    @java -jar ${SAXON}/saxon9he.jar -o:manifold/manifest.yml $< ${TRANSFORMDIR}/other/manifold_manifest.xsl

manifold/${MANUSCRIPTNAME}.zip: xhtml/*.html manifold/manifest.yml
    @echo -e "\n[BUILD] Generate Manifold package by collecting manifest.yml and HTML files\n"
    @if [ -e tei/media ]; then\
        cp -r tei/media manifold/;\
    else\
        mkdir -p manifold/media;\
    fi
    @cd xhtml && cp -r cover.html second_cover.html editorial.html *.css ../manifold/
# generate chapter html
    @java -jar ${SAXON}/saxon9he.jar -o:xhtml/sections.html xhtml/${MANUSCRIPTNAME}.html ${TRANSFORMDIR}/other/split_html_sections.xsl
    @cd manifold && zip -r ${MANUSCRIPTNAME}.zip manifest.yml media *.html *.css
# rm -rf manifold/media manifold/${MANUSCRIPTNAME}.html manifest.yml

clean:
    @echo "[BUILD] Delete everything but the Word DOCX manuscript"
    @rm -rf tei xhtml manifold pdf

问题是这些规则不起作用:

xhtml/*.html: tei/${MANUSCRIPTNAME}.xml

manifold/${MANUSCRIPTNAME}.zip: xhtml/*.html manifold/manifest.yml

使用通配符来查找需要由 make 构建的 target 是不正确的,因为当你 运行 make 第一次没有匹配的文件存在那些通配符,所以它们不能展开。

它似乎起作用的原因是,就像 shell 一样,如果通配符不匹配任何值,它 returns 通配符本身。因此,如果没有文件与 xhtml/*.html 匹配,则结果为文字字符串 xhtml/*.html。如果你有三个匹配的文件,那么结果就是像 xhtml/ONE.html xhtml/TWO.html xhtml/THREE.html.

这样的三个文件

所以第一次 运行 这个 makefile 没有匹配的目标,所以 make 想要构建一个目标,字面意思是 xhtml/*.html 并且有一个匹配该目标的规则,所以一切正常。

你第二次 运行 这个 makefile 有三个目标 make 想要构建并且有这样的规则:

xhtml/ONE.html xhtml/TWO.html xhtml/THREE.html: tei/${MANUSCRIPTNAME}.xml
        ...recipe...

这是什么意思?您可能认为这意味着 recipe 的一次调用将构建所有三个目标,但这不是它的意思。制作,这和写这个完全一样:

xhtml/ONE.html: tei/${MANUSCRIPTNAME}.xml
        ...recipe...
xhtml/TWO.html: tei/${MANUSCRIPTNAME}.xml
        ...recipe...
xhtml/THREE.html: tei/${MANUSCRIPTNAME}.xml
        ...recipe...

即每个目标运行配方一次。我无法理解大量 shell 脚本的作用,所以我不能说为什么它总是重建:一定是这些文件不是由目标实际创建的,或者它们的修改时间未设置正确。

如果这条规则真的用一次调用构建了所有 html 个文件,那么你需要使用某种伪目标来跟踪它的修改时间,像这样:

xhtml/.buildhtml: tei/${MANUSCRIPTNAME}.xml
        ... recipe ...
        @touch $@

manifold/${MANUSCRIPTNAME}.zip: xhtml/.buildhtml manifold/manifest.yml

或者,如果您知道您可以依赖最新的 GNU make 4.3 版本,您可以利用新的 "grouped targets" feature&:,并像这样编写您的 makefile:

ALLHTML = xhtml/ONE.html xhtml/TWO.html xhtml/THREE.html
$(ALLHTML) &: tei/${MANUSCRIPTNAME}.xml
        ...recipe...

manifold/${MANUSCRIPTNAME}.zip: $(ALLHTML) manifold/manifest.yml

(由于上述原因,您仍然不能使用 *.html)。