问题描述
我有一个 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
我怎样才能一次性完成构建,首先运行准备收集所有文件,然后根据需要编译和链接?
解决方法
正如 code_fodder 提到的,问题是源文件的创建。
基本上发生的事情是,你没有告诉 make 如何创建这些源文件,所以只要 make 知道它们不存在,也没有办法创建它们。因此,例如,当 make 想要构建 file1.o
时,它会查看您的模式规则并发现它可以从 file1.o
构建 file1.cpp
。然后它会寻找如何构建 file1.cpp
。不存在 file1.cpp
,并且没有任何规则可以让 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
并不是真的;确实,PREPARED SOURCE FILES 依赖于 prepare
。正如您的模式规则所示,目标文件仅依赖于“准备好的”源文件。应该写这个规则,而不是:
$(GEN_FILES): prepare
如果您使用 -j
执行此操作,一切都会如您所愿。
是的,这会变得混乱/困难。您遇到的问题是您可以指定先决条件列表 - 可以按顺序工作,但是一旦您开始使用 -j
然后 make 可以开始以任何旧顺序处理先决条件。所以 bin_file
需要 $(OBJ_FILES)
需要 prepare
。然后 %.o
需要同名的 %.cpp
文件 - 它可以为 main.o 做,但不能为 filex.o 做,因为它们还不存在 - 但它无论如何都会尝试并失败 - 平均而言time 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 已经存在使用 -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 阶段在您的 make
或 make build
调用中完成,然后状态 2 在 build
配方中的递归 make 调用中完成。