并行在所有子目录(-j)上调用gnumake,然后最后运行链接器规则(即顺序重要)

时间:2018-12-01 22:28:24

标签: c++ linux makefile gnu-make

我有一个c ++ makefile项目。它非常适合非平行建筑。对于并行构建,它可以工作99%...我唯一的问题是我无法使最终的可执行链接最后运行(必须是最后发生的事情)。

我有一些限制:我不想对链接行有任何PHONY依赖关系,因为这会使它每次都重新链接。即建立目标后,当我重新构建目标时,就不应重新链接它。

这是(稍作)最小的例子。请不要试图在它上面挖个洞,它只是在这里显示问题,不是真正的问题,而是我要显示的问题。您应该可以运行此程序,然后看到与我相同的问题。

# Set the default goal to build.
.DEFAULT_GOAL = build

#pretend subdirs (these don't really exist but it does not matter so long as they always try to be built)
MAKE_SUB_DIRS = 1 2 3

#pretend shared objects that are created by the pretend makefile sub directories (above)
OUTPUTS = out1.so out2.so out3.so

# Top level build goal - depends on all of the subdir makes and the target.out
.PHONY: build
build: $(MAKE_SUB_DIRS) target.out
    @echo build finished

# Takes 1 second to build each of these pretend sub make directories. PHONY so always runs
.PHONY: $(MAKE_SUB_DIRS)
$(MAKE_SUB_DIRS):
    @if [ ! -f out$@.so ] ; then echo making $@... ; sleep 1 ; echo a > out$@.so ; fi

# The main target, pretending that it needs out1,2 and 3 to link
# Should only run when target.out does not exist
# No PHONY deps allowed here
target.out:
    @echo linking $@...
    @ls $(OUTPUTS) > /dev/null
    @cat $(OUTPUTS) > target.out

# Clean for convinience
clean:
    @rm -rf *.so target.out

现在,我不太关心make的工作,我想要的是make -j的工作。这是我尝试运行的程序:

admin@osboxes:~/sandbox$ make clean 
admin@osboxes:~/sandbox$ 
admin@osboxes:~/sandbox$ make -j     - 1st attempt
making 1...
making 2...
linking target.out...
making 3...
ls: cannot access 'out1.so': No such file or directory
ls: cannot access 'out2.so': No such file or directory
ls: cannot access 'out3.so': No such file or directory
makefile:24: recipe for target 'target.out' failed
make: *** [target.out] Error 2
make: *** Waiting for unfinished jobs....
admin@osboxes:~/sandbox$ 
admin@osboxes:~/sandbox$ make -j     - 2nd attempt
linking target.out...
build finished
admin@osboxes:~/sandbox$ 
admin@osboxes:~/sandbox$ make -j     - 3rd attempt
build finished
admin@osboxes:~/sandbox$

因此,我重点介绍了运行它的三种尝试。

  • 尝试1:您可以看到构建的所有4个依赖项同时(大约)启动。由于每个makeing x...都需要1秒钟,而linking几乎是即时的,因此我们看到了我的错误。但是,所有三个“库”都是正确构建的。
  • 尝试2:仅当库尚不存在时才创建它们(这是bash代码-假装执行makefile可能执行的操作)。在这种情况下,它们已经创建。因此,链接现在通过了,因为它只需要存在这些库即可。
  • 尝试3:什么都不会发生,因为什么都不需要:)

因此,您可以看到所有步骤都在其中,只需订购它们即可。我希望make sub dirs 1, 2, 3以任何顺序并行构建,然后仅在它们全部完成后才运行target.out(即链接器)。

我不想这样称呼:$(MAKE) target.out,因为在我的真实makefile中,我有很多变量都已设置...

我尝试查看(从其他答案中).NOT_PARALLEL,并使用dep顺序运算符|(管道),并且尝试订购一组规则以使target.out成为最后一个....但是-j选项只是翻遍所有这些,破坏了我的订购:( ...必须有一些简单的方法来做到这一点?

3 个答案:

答案 0 :(得分:1)

通常,您只需将lib文件添加为target.out的先决条件:

target.out: $(OUTPUTS)
        @echo linking $@...

问题是,如果任何输出lib文件较新,这将重新链接target.out。通常,这就是您想要的(如果库已更改,则需要重新链接目标),但是您明确地说您没有。

GNU make提供了一个称为“仅先决条件”的扩展,您将其放在|之后:

target.out: | $(OUTPUTS)
        @echo linking $@...

现在,仅当target.out不存在时才会重新链接,但在那种情况下,它将一直等到$(OUTPUTS)完成构建后

如果您的$(OUTPUT)文件是由子公司制造的,则您可能会需要以下规则:

.PHONY: $(OUTPUT)
$(OUTPUT):
        $(MAKE) -C $$(dirname $@) $@

调用递归make,除非您有其他规则将在子目录中调用make

答案 1 :(得分:1)

编辑:添加一个将变量传递给子品牌的方法的示例。通过将$(SUBDIRS)添加到build的先决条件中而不是在其配方中进行一些优化。

我不确定我是否完全了解您的组织,但是一种解决子目录的解决方案如下。我假设,就像您的示例一样,构建子目录foo在顶层目录中产生foo.o。我还假设您的顶级Makefile定义了在构建子目录时要传递给子Make的变量(VAR1VAR2 ...)。

VAR1    := some-value
VAR2    := some-other-value
...
SUBDIRS := foo bar baz
SUBOBJS := $(patsubst %,%.o,$(SUBDIRS))

.PHONY: build clean $(SUBDIRS)

build: $(SUBDIRS)
    $(MAKE) top

$(SUBDIRS):
    $(MAKE) -C $@ VAR1=$(VAR1) VAR2=$(VAR2) ...

top: top.o $(SUBOBJS)
    $(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS)

top.o: top.cc
    $(CXX) $(CXXFLAGS) -c $< -o $@

clean:
    rm -f top top.o $(SUBOBJS)
    for d in $(SUBDIRS); do $(MAKE) -C $$d clean; done

这是并行安全的,并保证仅在所有子构建完成后才进行链接。请注意,您也可以export要传递给子make的变量,而不是在命令行中传递它们:

VAR1    := some-value
VAR2    := some-other-value
...
export VAR1 VAR2 ...

答案 2 :(得分:0)

好吧,所以我找到了“一个”解决方案...但是它有点违背我想要的,因此很丑陋(但不是那个那个丑陋的):

我可以尝试确保并行构建顺序的 only 方法是这样的:

rule: un ordered deps
rule:
    @echo this will happen last

这里将以任意顺序制作(或制作?)三个部门,然后最后将运行回声线。

但是我要做的是一条规则,特别是这样,以便它检查是否有任何更改或文件是否不存在-​​然后,然后只有这样,才能运行该规则。

我知道要从另一条规则的内部运行一条规则的唯一方法是递归调用该规则。但是,我在同一个makefile上递归调用make时遇到以下问题:

  1. 默认情况下不传递变量
  2. 许多相同的规则将被重新定义(不允许或不想要)

所以我想到了这个:

makefile:

# Set the default goal to build.
.DEFAULT_GOAL = build

#pretend subdirs (these don't really exist but it does not matter so long as they always try to be built)
MAKE_SUB_DIRS = 1 2 3

#pretend shared objects that are created by the pretend makefile sub directories (above)
OUTPUTS = out1.so out2.so out3.so

# Top level build goal - depends on all of the subdir makes and the target.out
export OUTPUTS
.PHONY: build
build: $(MAKE_SUB_DIRS)
    @$(MAKE) -f link.mk target.out --no-print-directory
    @echo build finished

# Takes 1 second to build each of these pretend sub make directories. PHONY so always runs
.PHONY: $(MAKE_SUB_DIRS)
$(MAKE_SUB_DIRS):
    @if [ ! -f out$@.so ] ; then echo making $@... ; sleep 1 ; echo a > out$@.so ; fi

# Clean for convinience
clean:
    @rm -rf *.so target.out

link.mk:

# The main target, pretending that it needs out1,2 and 3 to link
# Should only run when target.out does not exist
# No PHONY deps allowed here
target.out:
    @echo linking $@...
    @ls $(OUTPUTS) > /dev/null
    @cat $(OUTPUTS) > target.out

因此,在这里,我将链接器规则放入一个名为link.mk的单独的makefile中,这避免了对同一文件进行递归make调用(因此避免了重新定义的规则)。但是我必须导出需要传递的所有变量...这很丑陋,如果这些变量发生更改,则会增加一些维护开销。

...但是...有效:)

我不会在不久的将来对此进行标记,因为我希望一些天才会指出一种更整洁/更好的方法来实现这一目标...