【问题标题】:GNU Make Skipping Straight to LinkingGNU 直接跳过链接
【发布时间】:2021-02-05 22:43:28
【问题描述】:

我有一个 makefile,由于各种原因,它每次都依赖于一个支持的 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

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

【问题讨论】:

  • 这个解释隐藏了你的makefile的问题。您在此处显示的 makefile 无法按照您所说的方式运行。事实上,它不仅会等待构建目标文件,即使您不更改任何内容,它也会每次都重新构建所有目标文件,因为所有目标文件都依赖于prepare目标,即.PHONY。即使不是.PHONYprepare 规则也永远不会创建prepare 目标,因此它永远不会是最新的。
  • 请创建一个最小的完整示例,在运行它时显示问题,然后编辑您的问题以包含该示例。如果您的食谱实际上不运行编译器或运行任何内部命令(如copy_and_pp_files.py),这是最简单的,因为我们不知道它是做什么的。只需使用cptouch等来模拟创建和更新的文件。
  • 首先,我进行了编辑以删除任何非标准操作。这应该运行并重复问题。其次,简而言之就是这个问题。如何每次都先运行复制操作,然后允许 make 仅正确构建第一次操作中实际需要复制的文件?
  • @Squaven 我刚刚在答案中添加了一条关于您如何应对的注释,以避免总是重新编译您复制的文件...

标签: makefile prerequisites


【解决方案1】:

正如 code_fodder 所提到的,问题在于源文件的创建。

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

然后make看到目标:

$(OBJ_FILES) : prepare

所以它认为创建目标文件不需要配方,只运行链接行。下一次,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 这不是真的; 准备好的源文件依赖于prepare,这是真的。正如您的模式规则所示,目标文件仅依赖于“准备好的”源文件。应该写下这条规则:

$(GEN_FILES): prepare

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

【讨论】:

  • 我还没有尝试过,但这看起来不错 - 我的回答更像是一种蛮力和通用的方式来强制一些秩序。但这对于这个用例来说看起来更正确,很好
【解决方案2】:

是的,这会变得混乱/困难。您遇到的问题是您可以指定先决条件列表 - 可以按顺序工作,但是一旦您开始使用 -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 以任意顺序运行依赖项列表,但规则配方始终最后运行。

现在我们有了这个模式,我们可以填充其余部分。首先是生成文件的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++ 编译和链接阶段 - 这些将使用来自 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 已经存在 usingn -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 将所有先决条件拆分为一个单独的 make 会话,但这比我的方式更干净。感谢您的帮助!
  • 请注意:现在我已经看到了您是如何复制的 - 您可能会考虑使用类似 rsync 的东西来进行复制。问题是每次您复制文件时,filex.cpp 都已更新,需要重新编译。 rsync 使用时间戳进行了类似的检查。所以如果要复制的文件没有改变,那么rsync不会复制它,make也不会重新编译它。这应该使您不必总是重新编译复制的文件。您可以或多或少将cp 替换为rsync
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-23
  • 2017-09-11
  • 2021-04-17
  • 1970-01-01
  • 2013-10-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多