包含子目录的 makefile

makefile with subdirectories

我正在写一本书。这些章节将以 Markdown (.md) 编写,然后使用 pandoc 转换为 html 和 pdf(通过 LaTeX)版本。每章都有一些相关联的 Python 脚本,这些脚本会生成一些图像,并且需要 运行 在构建章节之前。我正在尝试编写一个将所有章节编译为这两种格式的 makefile。

目前,项目结构如下:

project
  |--- makefile
  |--- chapters
    | --- chapter1
      | --- main.md
      | --- genimage.py
      | --- genanotherimage.py
    | --- chapter2
      |--- main.md
      |--- otherimage.py
    | --- output
      | --- html
        | --- chapter1.html
        | --- chapter2.html
      | --- pdf
        | --- chapter1.pdf
        | --- chapter2.pdf

我想输入“make chapter1”(或类似的)并让它刷新 output/html/chapter1.html 和 output/pdf/chapter1.pdf,重新 运行如果它们已更改,则将相应目录中的所有 .py 脚本。理想情况下,我会有一个规则来并行处理所有章节,而不是每个章节都有一个单独的规则。 (实际生成 html/pdf 的命令是“pandoc -o output/html/chapter1.html chapter1/main.md”等等。)

我对make不是很熟悉,到目前为止我的尝试都没有成功。我无法设法在有多个文件要更新的地方建立目标,而且我还没有设法使用模式来处理具有单个规则的每一章。如果可以让事情变得更简单,我很乐意进行一些重组。

这个工作流程可以用 makefile 实现吗?我很感激任何开始的提示;我不知所措,即使知道在手册中查找正确的内容也会非常有帮助。

以下内容基于您显示的项目树并假定为 GNU make。它还假定您必须 运行 pandoc 和项目顶级目录中的 python 脚本。模式规则可能会有所帮助:

CHAPTERS := $(notdir $(wildcard chapters/chapter*))
.PHONY: all $(CHAPTERS)

all: $(CHAPTERS)

$(CHAPTERS): %: chapters/output/html/%.html chapters/output/pdf/%.pdf

chapters/output/html/%.html chapters/output/pdf/%.pdf: chapters/%/main.md
    for python_script in $(wildcard $(<D)/*.py); do ./$$python_script; done
    mkdir -p chapters/output/html chapters/output/pdf
    pandoc -o chapters/output/html/$*.html <other-options> $<
    pandoc -o chapters/output/pdf/$*.pdf <other-options> $<

主要的微妙之处在于,当 GNU make 遇到具有多个目标的模式规则时,它会认为单次执行配方会构建所有目标。在我们的例子中,HTML 和 PDF 输出是由相同的配方执行产生的。

Note: with recent versions of GNU make rules with grouped targets (&:) do the same.

这不是 100% 完美,因为如果您修改或添加 python 脚本,章节将不会重建。如果您也需要这个,我们将需要更复杂的 GNU make 功能,例如 secondary expansion or eval.

二次扩展示例:

CHAPTERS := $(notdir $(wildcard chapters/chapter*))
.PHONY: all $(CHAPTERS)

all: $(CHAPTERS)

$(CHAPTERS): %: chapters/output/html/%.html chapters/output/pdf/%.pdf

.SECONDEXPANSION:

chapters/output/html/%.html chapters/output/pdf/%.pdf: chapters/%/main.md $$(wildcard chapters/$$*/*.py)
    for python_script in $(wildcard $(<D)/*.py); do ./$$python_script; done
    mkdir -p chapters/output/html chapters/output/pdf
    pandoc -o chapters/output/html/$*.html $<
    pandoc -o chapters/output/pdf/$*.pdf $<

要了解 $$(wildcard chapters/$$*/*.py)$$ 的原因,请参阅 the GNU make manual

示例eval

CHAPTERS := $(notdir $(wildcard chapters/chapter*))
.PHONY: all $(CHAPTERS)

all: $(CHAPTERS)

$(CHAPTERS): %: chapters/output/html/%.html chapters/output/pdf/%.pdf

# : chapter
define CHAPTER_RULE
PYTHON_SCRIPTS_ := $$(wildcard chapters//*.py)

chapters/output/html/.html chapters/output/pdf/.pdf: chapters//main.md $$(PYTHON_SCRIPTS_)
    for python_script in $$(PYTHON_SCRIPTS_); do ./$$$$python_script; done
    mkdir -p chapters/output/html chapters/output/pdf
    pandoc -o chapters/output/html/.html $$<
    pandoc -o chapters/output/pdf/.pdf $$<
endef
$(foreach c,$(CHAPTERS),$(eval $(call CHAPTER_RULE,$c)))

要了解 $$$$$$ 的原因,请参阅 the GNU make manual