Parallel make多次生成代码

时间:2014-11-07 13:05:47

标签: makefile

让我们假设一个简单的项目,包括两个静态源和三个动态源,它们都是生成的。 makefile如下所示:

SRC_DIR = src
OUTPUT = output

TARGET = $(OUTPUT)/lib.so

GEN_SRCS = dynamic_1.c dynamic_2.c dynamic_3.c
SRCS = static_1.c static_2.c $(GEN_SRCS)

OBJS = $(SRCS:%.c=$(OUTPUT)/%.o)

all: $(TARGET)

$(TARGET): $(OBJS)
    gcc -shared -o $@ $^

$(OUTPUT)/%.o: $(SRC_DIR)/$*%.c | $(OUTPUT)
    @echo "Creating object $@ from $^"
    gcc -c -o $@ $<

$(SRC_DIR)/dynamic_%.c: definition_for_generator.xml
    @echo "---------------------------------------"
    @echo "Generating dynamic Cs"
    @echo "---------------------------------------"
    ./generate.sh $<

$(OUTPUT):
    mkdir -p $(OUTPUT)

.PHONY: clean
clean:
    rm -rf $(OUTPUT) $(SRC_DIR)/dynamic_1.c $(SRC_DIR)/dynamic_2.c $(SRC_DIR)/dynamic_3.c 

虽然只使用一个核心构建,但它构建得很好:

mkdir -p output
Creating object output/static_1.o from src/static_1.c
gcc -c -o output/static_1.o src/static_1.c
Creating object output/static_2.o from src/static_2.c
gcc -c -o output/static_2.o src/static_2.c
---------------------------------------
Generating dynamic Cs
---------------------------------------
./generate.sh definition_for_generator.xml
Creating object output/dynamic_1.o from src/dynamic_1.c
gcc -c -o output/dynamic_1.o src/dynamic_1.c
Creating object output/dynamic_2.o from src/dynamic_2.c
gcc -c -o output/dynamic_2.o src/dynamic_2.c
Creating object output/dynamic_3.o from src/dynamic_3.c
gcc -c -o output/dynamic_3.o src/dynamic_3.c
gcc -shared -o output/lib.so output/static_1.o output/static_2.o output/dynamic_1.o output/dynamic_2.o output/dynamic_3.o
rm src/dynamic_1.c src/dynamic_2.c src/dynamic_3.c

但是,对于并行构建(-j 2),dynamic_ *文件会生成两次:

mkdir -p output
---------------------------------------
Generating dynamic Cs
---------------------------------------
./generate.sh definition_for_generator.xml
---------------------------------------
Generating dynamic Cs
---------------------------------------
./generate.sh definition_for_generator.xml
---------------------------------------
Generating dynamic Cs
---------------------------------------
./generate.sh definition_for_generator.xml
Creating object output/static_1.o from src/static_1.c
gcc -c -o output/static_1.o src/static_1.c
Creating object output/static_2.o from src/static_2.c
gcc -c -o output/static_2.o src/static_2.c
Creating object output/dynamic_1.o from src/dynamic_1.c
gcc -c -o output/dynamic_1.o src/dynamic_1.c
Creating object output/dynamic_2.o from src/dynamic_2.c
gcc -c -o output/dynamic_2.o src/dynamic_2.c
Creating object output/dynamic_3.o from src/dynamic_3.c
gcc -c -o output/dynamic_3.o src/dynamic_3.c
gcc -shared -o output/lib.so output/static_1.o output/static_2.o output/dynamic_1.o output/dynamic_2.o output/dynamic_3.o
rm src/dynamic_1.c src/dynamic_2.c src/dynamic_3.c

我应该如何更改makefile以仅生成一个文件? 一条线索可以在这里,但我还没有找到使用它的方法: multiple targets from one recipe and parallel execution

要下载的项目位于:http://stuff.pitris.info/make.tar.gz

谢谢!

2 个答案:

答案 0 :(得分:1)

这个相关答案就是答案。你的规则:

$(SRC_DIR)/dynamic_%.c: definition_for_generator.xml
    @echo "---------------------------------------"
    @echo "Generating dynamic Cs"
    @echo "---------------------------------------"
    ./generate.sh $<

告诉make每个$(SRC_DIR)/dynamic_%.c的调用都会创建一个generate.sh文件。但是,运行该脚本实际上会生成多个文件(但make不知道)。

因此,当您运行make时,通常make按顺序进行操作,当它看到第一个dynamic_*.c文件时,它会运行脚本来生成该单个文件(但生成所有文件)。当make然后确定是否需要生成下一个dynamic_*.c文件时,它会看到该文件已经存在并跳过生成它的规则。

当您运行make -j生成并行进程时,那些恰好同时发生dynamic_*.c文件,因此他们所有运行脚本(即使只有其中一个需要)。

这就是为什么你看到它运行N次。更改目标订单/等。并且你可能会看到更少generate.sh次运行,因为并行性的运行方式不同(你甚至可以设法让它只运行一次),但这都是巧合。

正确的解决方案,就是在链接的答案中,通过列出该规则的多个模式来教授通过运行脚本来创建所有文件。或者,也可以将generate.sh修改为一次只生成一个文件(并始终运行N次)。


最简单(虽然有点难看)的方式来做多个模式的事情可能就是取代

GEN_SRCS = dynamic_1.c dynamic_2.c dynamic_3.c

GEN_SRCS = %ynamic_1.c %ynamic_2.c %ynamic_3.c

或类似的东西,然后替换

$(SRC_DIR)/dynamic_%.c: definition_for_generator.xml

$(GEN_SRCS): definition_for_generator.xml

答案 1 :(得分:0)

您的代码还有另一个问题:依赖于$(OUTPUT)目录。它永远不会按预期工作。

下面的makefile按预期工作,请尝试:

SRC_DIR = src
OUTPUT = output

TARGET = $(OUTPUT)/lib.so

GEN_SRCS = dynamic_1.c dynamic_2.c dynamic_3.c
SRCS = static_1.c static_2.c $(GEN_SRCS)

ifeq ($(filter clean,$(MAKECMDGOALS)),)
-include $(OUTPUT)/.timestamp
endif

OBJS = $(SRCS:%.c=$(OUTPUT)/%.o)

mkdir_@ = $(if $(wildcard $(@D)),,mkdir -p $(@D))

all: $(TARGET)

$(TARGET): $(OBJS)
    gcc -shared -o $@ $^

$(OUTPUT)/%.o: $(SRC_DIR)/$*%.c
    $(mkdir_@)
    @echo "Creating object $@ from $^"
    gcc -c -o $@ $<

$(OUTPUT)/.timestamp: definition_for_generator.xml
    $(mkdir_@)
    @echo "---------------------------------------"
    @echo "Generating dynamic Cs"
    @echo "---------------------------------------"
    ./generate.sh $<
    touch $@

.PHONY: clean
clean:
    rm -rf $(OUTPUT) $(SRC_DIR)/dynamic_1.c $(SRC_DIR)/dynamic_2.c $(SRC_DIR)/dynamic_3.c 

这个想法是为生成的空makefile注入依赖 - $(OUTPUT)/ .timestamp。这是为了强制GNU Make重读你的Makefile。生成$(OUTPUT)/。时间戳的规则运行源生成器。因此,当GNU Make第二次开始处理Makefile时,生成的源已经存在。因此,您不需要为它们定义任何依赖项,即它们就像您的常规源文件一样。

提示:将生成的源写入源代码树不是一个好主意,因为不能同时阻止多个构建使用此源代码树。这是这种方法最明显的问题。

另请参阅 mkdir _ @ 函数:如果目标不存在,它将有效地为目标创建目录,首先尝试使用GNU Make builtin在生成 mkdir之前检查目录是否存在过程。与Cygwin一起使用时会有所不同,因为与第一类UNIX系统相比,在此环境中创建进程非常耗时。

输出:

$ make -j
mkdir -p output
---------------------------------------
Generating dynamic Cs
---------------------------------------
./generate.sh definition_for_generator.xml
touch output/.timestamp
Creating object output/static_1.o from src/static_1.c
gcc -c -o output/static_1.o src/static_1.c
Creating object output/static_2.o from src/static_2.c
gcc -c -o output/static_2.o src/static_2.c
Creating object output/dynamic_1.o from src/dynamic_1.c
Creating object output/dynamic_2.o from src/dynamic_2.c
gcc -c -o output/dynamic_1.o src/dynamic_1.c
gcc -c -o output/dynamic_2.o src/dynamic_2.c
Creating object output/dynamic_3.o from src/dynamic_3.c
gcc -c -o output/dynamic_3.o src/dynamic_3.c
gcc -shared -o output/lib.so output/static_1.o output/static_2.o output/dynamic_1.o output/dynamic_2.o output/dynamic_3.o