Submakes 运行 多次相同的规则
Submakes run the same rule multiple times
我有一些使用并行多级 makefile 构建的软件,我发现当我的主 Makefile 运行 的两个独立目标来自具有相同依赖性的子 makefile 时,这种依赖性是 运行同时两次并创建一个错误。
考虑项目根文件夹中的以下主要 Makefile:
TARGETS = t1 t2 t3 t4 t5 t6 t7 t8
.PHONY: all $(TARGETS) clean
all: $(TARGETS)
$(TARGETS):
@echo Making $@
@sleep 1
$(MAKE) -C folder s$@
clean:
@echo Making $@
$(MAKE) -C folder clean
和子 makefile folder/Makefile:
SUBTARGETS = st1 st2 st3 st4 st5 st6 st7 st8
$(SUBTARGETS): dep
@echo Making $@
@sleep 1
@touch $@
dep:
@echo Making $@
@sleep 1
@echo bla >> dep
clean:
rm -f $(SUBTARGETS)
rm -f dep
rm -f dep2dump
然后 运行ning make -j8 在根文件夹中将 运行 目标 t1...t8 并行,然后 运行 子目标 st1...st8,它们都依赖于依赖性 dep。从 shell 输出和 dep 文件的内容(8 行)可以明显看出 dep 规则是 运行 8 次,就好像 8 次暗示的 folder/Makefile 是完全独立的。
我认为 submakes 在 运行ning 并行时协调,并且他们会避免 运行ning 同一目标两次,但似乎并非如此。
谁能建议解决这种情况的正确方法?
如果最终这是 make 不可避免的弱点,我应该研究哪些替代构建工具?
谢谢
编辑:MadScientist 和 Renaud Pacalet 的回答很有用,但并不能完全解决我的问题,因为它们都要求顶级 makefile 的作者了解子 makefile 的内部结构。虽然我没有在我的原始 post 中明确解释这个要求。
所以为了提供更多细节,我试图解决的用例是路径 folder/ 中的源代码是一个单独的项目,例如。一组实用程序 st1...st8 其中所有(或部分)实用程序都依赖于库 dep,文件夹 中实用程序项目的内部。然后我希望能够在各种主项目中(尽可能无缝地)使用这个子项目,每个项目只使用实用程序的一个(可能不同的)子集 st1.. .st8。此外,主项目可能包含许多目标 t1...t8,每个目标取决于 st1[= 的不同子集78=]...st8,如我上面的例子所示。目标 t1...t8 需要能够单独 运行 ,只构建子项目所需的依赖项(所以 make t1 只构建 st1, 等), 因此必须构建所有 st1... t1...t8 中的每一个都不需要 st8。另一方面,他们还需要能够并行运行,例如。通过 运行ning make all.
理想情况下,我不希望每个主 makefile 的作者都必须了解子项目的内部结构,也不必在子 makefile 中包含 st1[=78 的所有可能组合=]...st8 这样每个主项目都可以只调用其中一个来避免并行构建问题。
到目前为止我想到但没有测试过以下不完美的解决方案:
- 正如 Renaud 所建议的那样,使用 flock 之类的东西至少可以确保 dep 的多个 运行(通过单独的子制作实例)不会同时发生。缺点:需要安装额外的工具(flock 或类似工具)+ dep 运行s 多次,因此需要额外的工作来避免一遍又一遍地进行实际编译,否则只会吃性能成本。
- 在主 makefile 中包含子 makefile,这样所有内容 运行 都在一个 make 实例中。这需要使子生成文件能够工作,而不管包含它的主生成文件的路径如何。没什么大问题。缺点:合并/包含来自不同作者的两个 makefile 可以打开一堆蠕虫,即具有相同名称的变量等。
- 按照(2)中的描述修改子makefile + 在主项目中创建另一个makefile,例如。 utils.make,其中包含所需子 makefile 的目标规则,并包含子 makefile。所以 utils.make 将是(假设这个主项目只需要 st1、st5 和 st7 :
utils: st1 st5 st7
include foldes/Makefile
然后主 makefile 将有一个 utils-ext 规则作为每个 t1... 的依赖t8 将是:
utils-ext:
$(MAKE) -f rules.make utils
构建所需的所有实用程序。这使两个主要的 makefile 保持独立,但在构建 t1...t8 中的任何一个时构建了所有实用程序/子目标,这是次优的。
您可以尝试将 dep
依赖项移动到您的顶级 Makefile:
.PHONY: all $(TARGETS) clean dep
all: $(TARGETS)
$(TARGETS): dep
@echo Making $@
@sleep 1
$(MAKE) -C folder s$@
dep:
$(MAKE) -C folder s@
解决您的问题的唯一合适的方法是让一个 make 实例构建您想要的所有子目录目标。让父 make 在同一目录中并行调用多个子 make,除非每次调用都使用完全不相交的目标集,否则肯定会失败。因此,如果您有多个要在 submake 中执行的操作,您应该在 submake 的一次调用中将它们全部收集起来,并让 submake 的并行性为您处理。
你可以这样做:
TARGETS = t1 t2 t3 t4 t5 t6 t7 t8
.PHONY: all $(TARGETS) clean
all: $(TARGETS)
$(TARGETS): .submake ;
.submake:
$(MAKE) -C folder $(addprefix s,$(MAKECMDGOALS))
然后在子 make 中添加这个,以便在不带参数调用时构建所有内容:
all: $(SUBTARGETS)
在这里,如果你 运行 make
那么子 make 会在没有参数的情况下被调用并并行构建所有东西。如果您调用 make t1 t2
则使用参数 st1 st2
.
调用 submake
或者,您可以重新构建您的 makefile,这样您就根本不使用递归 make,一个 make 实例就知道所有不同的规则和依赖关系。
我有一些使用并行多级 makefile 构建的软件,我发现当我的主 Makefile 运行 的两个独立目标来自具有相同依赖性的子 makefile 时,这种依赖性是 运行同时两次并创建一个错误。 考虑项目根文件夹中的以下主要 Makefile:
TARGETS = t1 t2 t3 t4 t5 t6 t7 t8
.PHONY: all $(TARGETS) clean
all: $(TARGETS)
$(TARGETS):
@echo Making $@
@sleep 1
$(MAKE) -C folder s$@
clean:
@echo Making $@
$(MAKE) -C folder clean
和子 makefile folder/Makefile:
SUBTARGETS = st1 st2 st3 st4 st5 st6 st7 st8
$(SUBTARGETS): dep
@echo Making $@
@sleep 1
@touch $@
dep:
@echo Making $@
@sleep 1
@echo bla >> dep
clean:
rm -f $(SUBTARGETS)
rm -f dep
rm -f dep2dump
然后 运行ning make -j8 在根文件夹中将 运行 目标 t1...t8 并行,然后 运行 子目标 st1...st8,它们都依赖于依赖性 dep。从 shell 输出和 dep 文件的内容(8 行)可以明显看出 dep 规则是 运行 8 次,就好像 8 次暗示的 folder/Makefile 是完全独立的。 我认为 submakes 在 运行ning 并行时协调,并且他们会避免 运行ning 同一目标两次,但似乎并非如此。 谁能建议解决这种情况的正确方法? 如果最终这是 make 不可避免的弱点,我应该研究哪些替代构建工具? 谢谢
编辑:MadScientist 和 Renaud Pacalet 的回答很有用,但并不能完全解决我的问题,因为它们都要求顶级 makefile 的作者了解子 makefile 的内部结构。虽然我没有在我的原始 post 中明确解释这个要求。
所以为了提供更多细节,我试图解决的用例是路径 folder/ 中的源代码是一个单独的项目,例如。一组实用程序 st1...st8 其中所有(或部分)实用程序都依赖于库 dep,文件夹 中实用程序项目的内部。然后我希望能够在各种主项目中(尽可能无缝地)使用这个子项目,每个项目只使用实用程序的一个(可能不同的)子集 st1.. .st8。此外,主项目可能包含许多目标 t1...t8,每个目标取决于 st1[= 的不同子集78=]...st8,如我上面的例子所示。目标 t1...t8 需要能够单独 运行 ,只构建子项目所需的依赖项(所以 make t1 只构建 st1, 等), 因此必须构建所有 st1... t1...t8 中的每一个都不需要 st8。另一方面,他们还需要能够并行运行,例如。通过 运行ning make all.
理想情况下,我不希望每个主 makefile 的作者都必须了解子项目的内部结构,也不必在子 makefile 中包含 st1[=78 的所有可能组合=]...st8 这样每个主项目都可以只调用其中一个来避免并行构建问题。
到目前为止我想到但没有测试过以下不完美的解决方案:
- 正如 Renaud 所建议的那样,使用 flock 之类的东西至少可以确保 dep 的多个 运行(通过单独的子制作实例)不会同时发生。缺点:需要安装额外的工具(flock 或类似工具)+ dep 运行s 多次,因此需要额外的工作来避免一遍又一遍地进行实际编译,否则只会吃性能成本。
- 在主 makefile 中包含子 makefile,这样所有内容 运行 都在一个 make 实例中。这需要使子生成文件能够工作,而不管包含它的主生成文件的路径如何。没什么大问题。缺点:合并/包含来自不同作者的两个 makefile 可以打开一堆蠕虫,即具有相同名称的变量等。
- 按照(2)中的描述修改子makefile + 在主项目中创建另一个makefile,例如。 utils.make,其中包含所需子 makefile 的目标规则,并包含子 makefile。所以 utils.make 将是(假设这个主项目只需要 st1、st5 和 st7 :
utils: st1 st5 st7
include foldes/Makefile
然后主 makefile 将有一个 utils-ext 规则作为每个 t1... 的依赖t8 将是:
utils-ext:
$(MAKE) -f rules.make utils
构建所需的所有实用程序。这使两个主要的 makefile 保持独立,但在构建 t1...t8 中的任何一个时构建了所有实用程序/子目标,这是次优的。
您可以尝试将 dep
依赖项移动到您的顶级 Makefile:
.PHONY: all $(TARGETS) clean dep
all: $(TARGETS)
$(TARGETS): dep
@echo Making $@
@sleep 1
$(MAKE) -C folder s$@
dep:
$(MAKE) -C folder s@
解决您的问题的唯一合适的方法是让一个 make 实例构建您想要的所有子目录目标。让父 make 在同一目录中并行调用多个子 make,除非每次调用都使用完全不相交的目标集,否则肯定会失败。因此,如果您有多个要在 submake 中执行的操作,您应该在 submake 的一次调用中将它们全部收集起来,并让 submake 的并行性为您处理。
你可以这样做:
TARGETS = t1 t2 t3 t4 t5 t6 t7 t8
.PHONY: all $(TARGETS) clean
all: $(TARGETS)
$(TARGETS): .submake ;
.submake:
$(MAKE) -C folder $(addprefix s,$(MAKECMDGOALS))
然后在子 make 中添加这个,以便在不带参数调用时构建所有内容:
all: $(SUBTARGETS)
在这里,如果你 运行 make
那么子 make 会在没有参数的情况下被调用并并行构建所有东西。如果您调用 make t1 t2
则使用参数 st1 st2
.
或者,您可以重新构建您的 makefile,这样您就根本不使用递归 make,一个 make 实例就知道所有不同的规则和依赖关系。