我有一个makefile,由于各种原因,它依赖于支持的python脚本每次运行并从多个外部位置获取文件,复制到工作目录,并在编译之前通过单独的预处理器运行。
此生成文件必须能够并行运行 (-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.cpp
构建file1.o
。 因此,它正在寻找如何构建file1.cpp
. 不存在file1.cpp
,并且没有规则可以知道这将构建它,因此 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
这样做,一切都会随心所欲地等待。
是的,这变得混乱/困难。您遇到的问题是您可以指定先决条件列表 - 可以按顺序工作,但是一旦您开始使用-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
。您似乎无法仅使用依赖项来实现相同的顺序强制(至少不容易)。即,依赖项列表可以按任何顺序运行-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 的输出,使用 n-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
- 编译所有 1 个文件仍然只需要 4 秒。
把所有东西放在一起
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*
我尝试使用您的实际代码,但我无法对此进行测试,因此可能存在错误。为了清楚起见,我将其分为两个"阶段"。阶段 1 在make
或make build
调用中完成,然后状态 2 在build
配方中的递归 make 调用中完成。