GNU 使直接跳过链接

GNU Make Skipping Straight to Linking

我有一个生成文件,出于各种原因,它每次都依赖于支持 python 脚本到 运行,并从多个外部位置抓取文件,复制到工作目录,然后 运行在编译之前通过单独的预处理器。

这个makefile必须能够运行并行(-j8)所以不能保证处理的顺序。

在尝试明确指定先决条件时,我创建了一种情况,其中 make 跳过所有目标文件,直接进行链接,但由于必要的对象不存在而失败。第二个 运行,所有对象都已经存在(预处理步骤跳过已经存在的文件)并且所有文件都已正确编译和链接。

当 运行 没有 -j# 时一切正常,但是当我添加 -j2 时,跳过开始。

以下是生成文件示例:

GEN_FILES := file1.cpp file2.cpp file3.cpp
CXX_FILES := bin_main.cpp $(GEN_FILES)
OBJ_FILES := $(patsubst %.cpp,%.o,$(CXX_FILES))

.PHONY : all clean prepare
all : bin_file

prepare :
# Copy and preprocess all source files
    [ -f file1.cpp ] || cp d1/file1.cpp .
    [ -f file2.cpp ] || cp d2/file2.cpp .
    [ -f file3.cpp ] || cp d3/file3.cpp .

$(OBJ_FILES) : prepare

bin_file : $(OBJ_FILES)
    [ -f file1.o ] && [ -f file2.o ] && [ -f file3.o ] && touch bin_file

%.o : %.cpp
    @echo "Compiling $<..."
    [ -f $< ] && touch $@

clean :
    $(RM) *.o
    $(RM) file*
    $(RM) bin_file

我怎样才能一次性构建它,首先运行准备收集所有文件,然后根据需要进行编译和链接?

是的,这变得混乱/困难。您遇到的问题是您可以指定先决条件列表 - 可以按顺序工作,但是一旦您开始使用 -j 然后 make 就可以按任何旧顺序开始处理先决条件。所以 bin_file 需要 $(OBJ_FILES) 而需要 prepare。然后 %.o 需要相同名称的 %.cpp 文件 - 它可以为 main.o 做,但不能为 filex.o 做,因为它们还不存在 - 但它无论如何都会尝试并失败- 同时 make(并行)可能开始生成 .cpp 文件,但此时为时已晚...等...

我的先决条件构建模式

我在自己的设计中使用了一个非常具体的先决条件模式——有些人可能不赞成——但多年来我仔细考虑过这一点,发现它对我来说是最佳的。

我创建了一个名为 build 或其他规则的规则 - 它需要 build_prerequisites 目标,然后在完成后调用 make 进行实际构建:

.PHONY: build
build: build_prerequisites
build:
    @echo "start_build"
    @$(MAKE) bin_file

这意味着 build_prerequisites 总是 运行 在 recipe 运行 之前。您似乎无法仅使用依赖项来实现相同的顺序强制(至少不容易)。 IE。依赖项列表可以 运行 以任何顺序 -j,但规则配方是 always 运行 last.

现在我们有了这个模式,我们可以填充剩下的部分。首先是生成文件的 build_prerequisites 目标——我在示例中使用了 echo,因为我没有您的 python 脚本:

.PHONY: build_prerequisites
build_prerequisites:
    @echo "build_prerequisites"
    echo "create file1" > file1.cpp
    echo "create file2" > file2.cpp
    echo "create file3" > file3.cpp

最后添加 c++ 编译和 link 阶段 - 这些将是 运行 以及来自 build 的单个递归 make 调用 - 即 $(MAKE) bin_file(我又是在我的示例中使用 echo 创建文件):

%.o : %.cpp
    @echo "compiling: $<"
    @#echo "$(CXX) $(SRC_INCLUDES) $(LIB_INCLUDES) $(CXXFLAGS) -c $< -o $@"
    @echo "touch" > $@

bin_file : $(OBJ_FILES) 
    @echo "linking: $<"
    @echo $(CXX) $(SRC_INCLUDES) $^ $(LIB_INCLUDES) $(LDFLAGS) -o $@
    @echo "touch" > $@

输出

这是我的测试程序的输出(使用 echo)并且 main.cpp 已经存在使用 -j10:

make -j10
build_prerequisites
echo "create file1" > file1.cpp
echo "create file2" > file2.cpp
echo "create file3" > file3.cpp
start_build
make[1]: Entering directory '/mnt/d/software/ubuntu/make'
compile: bin_main.cpp
compile: file1.cpp
compile: file2.cpp
compile: file3.cpp
link: bin_main.o
g++ bin_main.o file1.o file2.o file3.o -o bin_file
make[1]: Leaving directory '/mnt/d/software/ubuntu/make'

注意:如果我在“编译”规则中放入 sleep 1 - 编译所有 4 个文件仍然只需要 1 秒。

把它们放在一起

GEN_FILES := file1.cpp file2.cpp file3.cpp
CXX_FILES := bin_main.cpp $(GEN_FILES)
OBJ_FILES := $(patsubst %.cpp,%.o,$(CXX_FILES))

###### STAGE 1

.PHONY: build
build: build_prerequisites
build:
    @echo "start_build"
    @$(MAKE) bin_file

.PHONY: build_prerequisites
build_prerequisites:
    @echo "build_prerequisites"
    copy_and_pp_files.py $(CXX_FILES) $(SEARCH_DIRS) .
    copy_and_pp_files.py $(CFG_FILES) $(SEARCH_DIRS) .

###### STAGE 2

%.o : %.cpp
    @echo "compiling: $<"
    @$(CXX) $(SRC_INCLUDES) $(LIB_INCLUDES) $(CXXFLAGS) -c $< -o $@

bin_file : $(OBJ_FILES) 
    @echo "linking: $<"
    @$(CXX) $(SRC_INCLUDES) $^ $(LIB_INCLUDES) $(LDFLAGS) -o $@

###### OTHER RULES

.PHONY: clean
clean :
    @$(RM) *.o
    @$(RM) file*

我曾尝试使用您的实际代码,但我无法对此进行测试,因此其中可能存在错误。为了清楚起见,我将其分为 2 个“阶段”。阶段 1 在您的 makemake build 调用中完成,然后状态 2 在 build 配方中的递归 make 调用中完成。

正如 code_fodder 提到的问题是源文件的创建。

基本上发生的事情是,您没有告诉 make 如何创建这些源文件,因此 make 知道它们不存在并且无法创建它们。因此,例如,当 make 想要构建 file1.o 时,它会查看您的模式规则并发现它可以从 file1.cpp 构建 file1.o。然后它会寻找如何构建 file1.cpp。不存在 file1.cpp,并且没有 make 知道会构建它的规则,因此 make 会忽略该模式规则,因为它不匹配。

然后make看到目标:

$(OBJ_FILES) : prepare

所以它认为创建目标文件不需要配方,只运行 link 行。下一次,make 会看到准备好的源文件(来自以前的构建),然后它可以使用您的模式规则。

如果您将模式规则更改为静态模式规则,您明确地告诉 make 使用什么规则,而不是为它提供一个可能的规则来使用,如果不匹配它可以忽略(这是什么是模式规则),你会看到错误:

$(OBJ_FILES): %.o : %.cpp
        @echo "Compiling $<..."
        sleep 1
        [ -f $< ] && touch $@

会告诉你:

make: *** No rule to make target 'file1.cpp', needed by 'file1.o'.  Stop.

请记住,make 在实际构建任何内容之前正在寻找匹配模式规则:它不想构建每个可能的匹配模式规则的每个可能的先决条件,以决定是否在规则的末尾可以使用。该规则是根据文件系统的当前状态加上您给出的规则进行匹配的可以进行更改。 Make 不知道如果它要调用 prepare 目标,它正在寻找的源文件会神奇地出现。

你的基本问题是这个语句是错误的依赖关系:

$(OBJ_FILES) : prepare

目标文件依赖于prepare是不正确的;事实是 PREPARED SOURCE FILES 依赖于 prepare。正如您的模式规则所示,目标文件仅依赖于“准备好的”源文件。这个规则应该写成:

$(GEN_FILES): prepare

如果您使用 -j 执行此操作,一切都会按您的意愿等待。