如何编写 Makefile 来运行目标文件名事先未知但在第一步之后的构建过程?

问题描述 投票:0回答:1

上下文: 我有一个构建过程,其第一步生成 ~20k 文件,这些文件具有不可预测但确定的文件名。其余步骤对每个文件进行操作,并生成具有相同名称但扩展名不同的新文件。

构建过程的简化 shell 版本可能如下所示:

./generate_testcases spec.json # Generates many .txt files
find -type f -name '*.txt' -exec ./run_test_case {} \; # Generates a .log file for each .txt file
find -type f -name '*.log' -exec ./grab_test_data {} \; # Generates a .csv file for each .log file

出于多种原因,我想在

make
中执行此操作,但特别是因为后两个步骤容易失败,最好再次运行
make
并让它只运行实际需要它的文件。

这些步骤本身对于模式规则来说是相当微不足道的:

%.txt : spec.json
    ./generate_testcases spec.json
%.log : %.txt
    ./run_test_case $<
%.csv : %.log
    ./grab_test_data $<

问题是在第一步运行之前我无法定义构建目标(例如

all
)。我无法控制第一步,无法提前获取文件名列表。


我首先尝试了 Secondary Expansion

$$(shell find ...)
作为先决条件,但即使这样运行也太早了,因为 make 必须在运行任何食谱之前进行第二次扩展以构建依赖关系树。

然后我想为什么不使用某种标记

file_list
在根本没有文件的情况下运行第一步,并从该文件中获取
.txt
文件列表以供将来运行:

text_files := $(shell cat file_list)

.PHONY: all
all : file_list $(text_files)

file_list : spec.json
   ./generate_testcases spec.json
   find -type f -name '*.txt' > file_list

%.txt : spec.json
   ./generate_testcases spec.json
   find -type f -name '*.txt' > file_list

这里有一些问题,包括

file_list
可能包含新的
spec.json
不再生成的旧文件,但我现在愿意接受它。这个想法对后两个步骤也没有帮助;它没有给我一种将
.log
.csv
文件添加到
all
目标的方法。


我的下一个想法是,“好吧,我可以运行 make 两次”,我可以在

Makefile
本身中以编程方式做到这一点:

text_files := $(shell cat file_list 2> /dev/null || true)
log_files := $(patsubst %.txt,%.log,$(text_files))
csv_files := $(patsubst %.txt,%.csv,$(text_files))

.PHONY: all
all : file_list $(text_files) $(log_files) $(csv_files)

file_list : spec.json
   ./generate_testcases spec.json
   find -type f -name '*.txt' > file_list
   $(MAKE)

%.txt : spec.json
   ./generate_testcases spec.json
   find -type f -name '*.txt' > file_list
   $(MAKE)
%.log : %.txt
    ./run_test_case $<
%.csv : %.log
    ./grab_test_data $<

当什么都不存在的时候,

file_list
还是会被创建,
make
会再次被调用。孩子
make
将有一个
file_list
所以
text_files
和其他因变量将被填充,构建的其余部分将按需要运行。如果
file_list
是最新的,但缺少其他部分,那么第一个
make
将负责其他步骤,而无需运行孩子
make
.

问题是当

file_list
都过时了,并且相对于过时的
file_list
缺少部分:
file_list
将得到更新,孩子
make
将被调用并运行所有内容,但随后由于旧的
make
.
,父母
file_list

仍将运行它认为需要的过时构建步骤

我可能可以忍受这些问题,但如果可能的话,我想轻松解决它们。 有更好的方法吗?

makefile gnu-make
1个回答
0
投票

你调用子 make 的想法绝对是处理这个问题的最简单和最清晰的方法。

为了避免多次构建,您只需要让 top-makefile 不依赖于任何最终输出,并且始终调用子 make。它会是这样的:

text_files := $(shell cat file_list 2> /dev/null)
log_files := $(patsubst %.txt,%.log,$(text_files))
csv_files := $(patsubst %.txt,%.csv,$(text_files))

.PHONY: all
all : file_list
        $(MAKE) _build_inner

file_list : spec.json
        ./generate_testcases $<
        find -type f -name '*.txt' > $@

.PHONY: _build_inner
_build_inner: $(log_files) $(csv_files)

  ...
© www.soinside.com 2019 - 2024. All rights reserved.