GNU如何为不在同一目录中的文件制定静态模式规则?

mep*_*erp 4 bash shell makefile

我想使用make并创建一个静态模式规则,该规则的目标位于output目录中,并且前提文件位于先前的目录中,并且必须递归工作。

我这里有一个最小的例子:

.
??? anotherdir
?   ??? output
?   ?   ??? source3.md
?   ?   ??? source4.md
?   ??? source3.json
?   ??? source4.json
??? output
?   ??? source1.md
?   ??? source2.md
??? source1.json
??? source2.json
Run Code Online (Sandbox Code Playgroud)

我要生成output的目录,如果它们不存在,我想生成*.md从文件中*.json使用make,如果它们不存在,或*.json将被更新。

到目前为止,我有以下内容Makefile

SOURCE_FILES := $(shell find ./ -name "*.json")
OUTPUT_FILES := $(join $(addsuffix output/,$(dir $(SOURCE_FILES))), $(addsuffix .md,$(basename $(notdir $(SOURCE_FILES)))))

.PHONY: all
all: $(OUTPUT_FILES)

$(OUTPUT_FILES): %.md: %.json
    mkdir -p $(dir $@)
    # Command to create MD file from json file into the output directory here
Run Code Online (Sandbox Code Playgroud)

从json文件创建MD文件的实际命令在这里无关紧要,因为我有一个脚本可以调用,该脚本将为我执行此操作。这里的问题是,当我甚至尝试运行此命令时,都会得到以下输出:

> make all
make: *** No rule to make target 'anotherdir/output/source4.json', needed by 'anotherdir/output/source4.md'.  Stop.
Run Code Online (Sandbox Code Playgroud)

显然,source4.json它不在中anotherdir/output,而是在前面的目录中,它只是anotherdir。我不知道如何制作图案$(OUTPUT_FILES): %.md: %.json才能正确匹配它。

还是静态模式规则在这里不好?我不确定该怎么做才能适应我的情况。

编辑:我试图做这样的事情:

$(OUTPUT_FILES): %.md: $(join $(subst output,,$(dir %)), $(addsuffix .json,$(basename $(notdir %))))
Run Code Online (Sandbox Code Playgroud)

而且这不起作用,我仍然得到:

> make all
make: *** No rule to make target 'anotherdir/output/source4.json', needed by 'anotherdir/output/source4.md'.  Stop.
Run Code Online (Sandbox Code Playgroud)

编辑2:澄清一下,我从以下文件开始

.
??? anotherdir
?   ??? source3.json
?   ??? source4.json
??? source1.json
??? source2.json
Run Code Online (Sandbox Code Playgroud)

然后当我运行make时,我希望它生成这样的输出文件夹

.
??? anotherdir
?   ??? output
?   ?   ??? source3.md
?   ?   ??? source4.md
?   ??? source3.json
?   ??? source4.json
??? output
?   ??? source1.md
?   ??? source2.md
??? source1.json
??? source2.json
Run Code Online (Sandbox Code Playgroud)

我想使用某种智能的makefile语法来挑选这些文件名,而无需自己进行硬编码。因此,我查看了文档,发现静态模式规则可能是我想要的解决方案,只是我无法获得正确的先决条件模式。

Bet*_*eta 5

我会这样:

首先,按照您的方式查找源文件(稍作改动以防止难看的双斜杠):

SOURCE_FILES := $(shell find . -name "*.json")
Run Code Online (Sandbox Code Playgroud)

如果我们可以一次使用两个通配符,则模式文件会很好,但是Make不能完全做到这一点。因此,我建议使用模板:

define template
TDIR := $(dir $(1))output
TARG := $$(TDIR)/$(notdir $(basename $(1))).md
$$(TARG): $(1)
    mkdir -p $$@
    @echo building $$@ from $$<
    # Command to create MD file from json file into the output directory here
endef

$(foreach SOURCE,$(SOURCE_FILES),$(eval $(call template,$(SOURCE))))
Run Code Online (Sandbox Code Playgroud)

如果这行得通,剩下的就是构造输出文件列表,以及将所有这些文件作为先决条件的默认规则:

define template
TDIR := $(dir $(1))output
TARG := $$(TDIR)/$(notdir $(basename $(1))).md
OUTPUT_FILES += $$(TARG)
$$(TARG): $(1)
    mkdir -p $$@
    @echo building $$@ from $$<
    # Command to create MD file from json file into the output directory here
endef

all:

$(foreach SOURCE,$(SOURCE_FILES),$(eval $(call template,$(SOURCE))))

all: $(OUTPUT_FILES)
Run Code Online (Sandbox Code Playgroud)

它虽然不漂亮,但似乎可以正常工作。


Ren*_*let 3

如果没有在另一个答案中提出,我会建议foreach-eval-call。为了完整起见,这里有 GNU make 的不同解决方案(它们也可能适用于其他版本的 make,但我没有检查):

预先创建输出目录

../%.json如果输出目录已经存在,您可以在模式规则中引用:

SOURCE_FILES := $(shell find . -name "*.json")
OUTPUT_FILES := $(join $(dir $(SOURCE_FILES)),\
    $(patsubst %.json,output/%.md,$(notdir $(SOURCE_FILES))))
$(shell mkdir -p $(dir $(OUTPUT_FILES)))

.PHONY: all
all: $(OUTPUT_FILES)

%.md: ../%.json
    : json2md $< -o $@
Run Code Online (Sandbox Code Playgroud)

这可能看起来很奇怪,但如果你仔细阅读GNU make 手册的模式匹配部分,你应该很快就能理解。其工作的唯一限制是输出目录在与目标匹配的搜索模式规则之前存在。如果不存在,make 会抱怨没有符合条件的规则来构建目标。这是以下原因:

$(shell mkdir -p $(dir $(OUTPUT_FILES)))
Run Code Online (Sandbox Code Playgroud)

在 Makefile 的开头。示范:

$ make
: json2md output/../source2.json -o output/source2.md
: json2md output/../source1.json -o output/source1.md
: json2md anotherdir/output/../source4.json -o anotherdir/output/source4.md
: json2md anotherdir/output/../source3.json -o anotherdir/output/source3.md
Run Code Online (Sandbox Code Playgroud)

使用二次扩展

二次扩展使您可以在先决条件列表中使用自动变量。需要它们$$来逃避 make 的第一次扩展。

SOURCE_FILES := $(shell find . -name "*.json")
OUTPUT_FILES := $(join $(dir $(SOURCE_FILES)),\
    $(patsubst %.json,output/%.md,$(notdir $(SOURCE_FILES))))

.PHONY: all
all: $(OUTPUT_FILES)

$(sort $(dir $(OUTPUT_FILES))):
    mkdir -p $@

.SECONDEXPANSION:
$(OUTPUT_FILES): $$(patsubst %output,%,$$(@D))$$(basename $$(@F)).json | $$(dir $$@)
    : json2md $< -o $@
Run Code Online (Sandbox Code Playgroud)

示范:

$ make
mkdir -p output/
mkdir -p anotherdir/output/
: json2md source2.json -o output/source2.md
: json2md source1.json -o output/source1.md
: json2md anotherdir/source4.json -o anotherdir/output/source4.md
: json2md anotherdir/source3.json -o anotherdir/output/source3.md
Run Code Online (Sandbox Code Playgroud)

注意:我没有在 json-to-md 规则中创建输出目录(其缺点是多次创建它们),而是将它们添加为仅订单先决条件,并添加了特定规则来创建它们。

注意:该sort函数还可以删除重复项。

使用递归make

在这里,我们在每个子目录中递归调用 make(始终使用相同的 Makefile)(当然output,除了 )。每次调用仅处理本地 json 文件,这使得先决条件和目标的路径更加简单。

MF           := $(realpath $(lastword $(MAKEFILE_LIST)))
SUB_DIRS     := $(filter-out . ./output,$(shell find . -maxdepth 1 -type d))
SOURCE_FILES := $(filter-out $(SUB_DIRS),$(wildcard *.json))
OUTPUT_FILES := $(patsubst %.json,output/%.md,$(SOURCE_FILES))

.PHONY: $(SUB_DIRS) all
all: $(SUB_DIRS) $(OUTPUT_FILES)

$(OUTPUT_FILES): output/%.md: %.json | output
    : json2md $< -o $@

output:
    mkdir -p $@

$(SUB_DIRS):
    $(MAKE) -C $@ -f $(MF)
Run Code Online (Sandbox Code Playgroud)

示范:

$ make
make -C anotherdir -f /home/doe/json2md/Makefile
make[1]: Entering directory '/home/doe/json2md/anotherdir'
mkdir -p output
: json2md source4.json -o output/source4.md
: json2md source3.json -o output/source3.md
make[1]: Leaving directory '/home/doe/json2md/anotherdir'
mkdir -p output
: json2md source2.json -o output/source2.md
: json2md source1.json -o output/source1.md
Run Code Online (Sandbox Code Playgroud)