处理生成文件中的所有符号组合



假设我设计了一个C++库,我想广泛测试所有功能。 其中一些功能是在构建时通过已定义或未定义的符号定义的。

// library.h
A foo( const B& b )
{
#ifdef OPTION_X
... do it that way
#else
... do it another way
#endif
}

我构建了一个测试程序,我想为所有可能的配置构建和运行该程序,以确保所有测试都通过:

// mytest.cpp
#include "library.h"
int main()
{
... some test code
#ifdef OPTION_X
... do it that way
#else
... do it that other way
#endif
... more stuff with more options
}

如果我有 1 个选项(称为"A"),如果它"打开",我想运行测试 (选项"A"_AY:是)或"关闭"(选项"A"_AN:否)

我的制作文件保存了这个:

.PHONY: test
test: BUILD/mytest_AY BUILD/mytest_AN
BUILD/mytest_AY
BUILD/mytest_AN
BUILD/mytest_AY: CXXFLAGS+=-DOPTION_A
BUILD/mytest_AY: mytest.cpp
$(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS)
BUILD/mytest_AN: mytest.cpp
$(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS)

这很好。

但是现在,如果我有 2 个选项要测试(比如"A"和"B"),你会看到重点: 我将有 4 个目标来构建和运行:

test: BUILD/mytest_AYBY BUILD/mytest_ANBY BUILD/mytest_AYBN BUILD/mytest_ANBN
BUILD/mytest_AYBY
BUILD/mytest_ANBY
BUILD/mytest_AYBN
BUILD/mytest_ANBN
BUILD/mytest_AYBN: CXXFLAGS+=-DOPTION_A
BUILD/mytest_AYBY: CXXFLAGS+="-DOPTION_A -DOPTION_B"
BUILD/mytest_ANBY: CXXFLAGS+=-DOPTION_B
BUILD/mytest_AYBY: mytest.cpp
$(CXX) $(CFLAGS) -o $@ $< $(LDFLAGS)
BUILD/mytest_ANBY: mytest.cpp
$(CXX) $(CFLAGS) -o $@ $< $(LDFLAGS)
BUILD/mytest_AYBN: mytest.cpp
$(CXX) $(CFLAGS) -o $@ $< $(LDFLAGS)
BUILD/mytest_ANBN: mytest.cpp
$(CXX) $(CFLAGS) -o $@ $< $(LDFLAGS)

我有两个问题:

  • 有没有办法有一个规则/食谱而不是四个? 除了目标名称外,都是一样的。

  • 此方法虽然适用于 1 或 2 个生成时选项,但不能很好地扩展。 3 已经很麻烦了,更重要的是,它变成了一场噩梦。 你会如何处理这种情况?

有趣的问题。如果你有GNU make,你可以尝试如下。编译和执行命令只是回显;当您对所看到的感到满意时,请取下两个echo@消音器。有3个选项:ABC;如果需要,请添加更多内容,或使用make test ONAMES="A B C D E F"在命令行上指定它们。

# option names
ONAMES   := A B C
# number of options
ONUM     := $(words $(ONAMES))
# configuration binary strings (e.g., 000 001 ... 111)
CONFIGS  := $(shell echo "obase=2; for(i=0; i<2^$(ONUM); i++) 2^$(ONUM)+i" | 
bc | sed 's/1//')
# one space
SPACE    := $(NULL) $(NULL)
# list of all test binaries
TESTBINS :=
# the macro used to instantiate a test
# $(1): the configuration binary string (e.g. 101)
define TEST_macro
# the configuration list in NAMEY/N format (e.g., AY BN CY)
config-$(1)  := $$(join $(ONAMES),$$(subst 1,Y ,$$(subst 0,N ,$(1))))
# the test binary (e.g., BUILD/mytest_AYBNCY)
test-$(1)    := BUILD/mytest_$$(subst $$(SPACE),,$$(config-$(1)))
# the list of active options (e.g., A C)
options-$(1) := $$(subst Y,,$$(filter %Y,$$(config-$(1))))
# add the test binary to list of all test binaries
TESTBINS     += $$(test-$(1))
# target-specific compilation options
$$(test-$(1)): CXXFLAGS += $$(addprefix -DOPTION_,$$(options-$(1)))
endef
# apply macro on each configuration binary string
$(foreach c,$(CONFIGS),$(eval $(call TEST_macro,$(c))))
# one phony target per test run (e.g., BUILD/mytest_AYBNCY.run)
TESTRUNS := $(addsuffix .run,$(TESTBINS))
.PHONY: $(TESTRUNS)
# one phony target for all test runs
.PHONY: test
test: $(TESTRUNS)
# static pattern rule for the test runs
$(TESTRUNS): %.run: %
@echo "./$<"
# rule for the test binaries
$(TESTBINS): mytest.cpp
@echo "$(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS)"

演示:

$ make test
g++ -o BUILD/mytest_ANBNCN mytest.cpp
g++ -DOPTION_C -o BUILD/mytest_ANBNCY mytest.cpp
g++ -DOPTION_B -o BUILD/mytest_ANBYCN mytest.cpp
g++ -DOPTION_B -DOPTION_C -o BUILD/mytest_ANBYCY mytest.cpp
g++ -DOPTION_A -o BUILD/mytest_AYBNCN mytest.cpp
g++ -DOPTION_A -DOPTION_C -o BUILD/mytest_AYBNCY mytest.cpp
g++ -DOPTION_A -DOPTION_B -o BUILD/mytest_AYBYCN mytest.cpp
g++ -DOPTION_A -DOPTION_B -DOPTION_C -o BUILD/mytest_AYBYCY mytest.cpp
./BUILD/mytest_ANBNCN
./BUILD/mytest_ANBNCY
./BUILD/mytest_ANBYCN
./BUILD/mytest_ANBYCY
./BUILD/mytest_AYBNCN
./BUILD/mytest_AYBNCY
./BUILD/mytest_AYBYCN
./BUILD/mytest_AYBYCY

解释:

我们首先生成从000111的 2^3=8 个二进制字符串,例如,借助shellmake 函数,bc。我们将结果分配给 make 变量CONFIGS。这些二进制字符串对应于 3 个选项的所有可能配置,其中1表示是 (Y),0表示否 (N)。

然后,我们使用TEST_macro宏处理这些配置字符串中的每一个。这是foreach-eval-call结构进入图片的地方。通过混合joinsubst和其他make函数,我们转换每个二进制字符串。例如,101首先转换为AY BN CY列表(变量config-101),接下来转换为BUILD/mytest_AYBNCY测试二进制名称(变量test-101),然后转换为启用选项的A C列表(变量options-101)。最后,启用选项的列表又转换为具有addprefix的目标特定CXXFLAGS值。

注意:如果您有一台多核计算机并且想要与make -jN并行运行N测试,那么每次测试运行都有一个单独的虚假目标(例如,BUILD/mytest_AYBNCY.run)很有趣。

注意:除了一个特定方面外,所有这些都相当简单:宏按 make 扩展了两次。一次作为eval函数的参数,一次作为生成的 make 构造由 make 重新解析时。这就是为什么我们几乎在所有我们通常只能找到$的地方使用$$:以逃避第一次扩展。

最新更新