处理 makefile 中的所有符号组合

Handling all symbols combinations in makefile

假设我设计了一个 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”),我想 运行 测试它是否“打开” (_AY 对于选项“A”:是)或“关闭”(_AN 对于选项“A”:否)

我的 makefile 包含这个:

.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)

我有两个问题:

有趣的问题。如果你有 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 个二进制字符串,例如 bc 感谢 shell make 函数。我们将结果分配给 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)。最后,启用选项列表依次转换为目标特定的 CXXFLAGSaddprefix.

Note: having one separate phony target per test run (e.g., BUILD/mytest_AYBNCY.run) is interesting if you have a multi-core computer and you want to run N tests in parallel with make -jN.

Note: all this is rather simple except one specific aspect: the macro is expanded twice by make. Once as parameter of the eval function and once more when the resulting make construct is re-parsed by make. This is why we use $$ almost everywhere we would normally find just $: to escape the first expansion.