处理 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)
我有两个问题:
有没有办法用一个 rule/recipe 而不是四个?
除了目标名称外,其他的都一样。
这种方法虽然适用于 1 或 2 个构建时选项,但不能很好地扩展。
3 已经很麻烦了,更多,它变成了一场噩梦。
你会如何处理这种情况?
有趣的问题。如果你有 GNU make,你可以试试下面的方法。编译和执行命令只是回显;当您对所看到的感到满意时,请移除两个 echo
和 @
消音器。有3个选项:A
、B
和C
;如果您愿意,可以添加更多或在命令行中使用 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
解释:
我们首先生成从 000
到 111
的 2^3=8 个二进制字符串,例如 bc
感谢 shell
make 函数。我们将结果分配给 make 变量 CONFIGS
。这些二进制字符串对应于 3 个选项的所有可能配置,其中 1
表示是 (Y
),0
表示否 (N
)。
然后,我们使用 TEST_macro
宏处理每个配置字符串。这是 foreach-eval-call
构造进入画面的地方。通过混合使用 join
、subst
和其他 make 函数,我们可以转换每个二进制字符串。例如,101
首先转换为 AY BN CY
列表(变量 config-101
),然后转换为 BUILD/mytest_AYBNCY
测试二进制名称(变量 test-101
),然后转换为A C
启用选项列表(变量 options-101
)。最后,启用选项列表依次转换为目标特定的 CXXFLAGS
值 addprefix
.
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.
假设我设计了一个 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)
我有两个问题:
有没有办法用一个 rule/recipe 而不是四个? 除了目标名称外,其他的都一样。
这种方法虽然适用于 1 或 2 个构建时选项,但不能很好地扩展。 3 已经很麻烦了,更多,它变成了一场噩梦。 你会如何处理这种情况?
有趣的问题。如果你有 GNU make,你可以试试下面的方法。编译和执行命令只是回显;当您对所看到的感到满意时,请移除两个 echo
和 @
消音器。有3个选项:A
、B
和C
;如果您愿意,可以添加更多或在命令行中使用 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
解释:
我们首先生成从 000
到 111
的 2^3=8 个二进制字符串,例如 bc
感谢 shell
make 函数。我们将结果分配给 make 变量 CONFIGS
。这些二进制字符串对应于 3 个选项的所有可能配置,其中 1
表示是 (Y
),0
表示否 (N
)。
然后,我们使用 TEST_macro
宏处理每个配置字符串。这是 foreach-eval-call
构造进入画面的地方。通过混合使用 join
、subst
和其他 make 函数,我们可以转换每个二进制字符串。例如,101
首先转换为 AY BN CY
列表(变量 config-101
),然后转换为 BUILD/mytest_AYBNCY
测试二进制名称(变量 test-101
),然后转换为A C
启用选项列表(变量 options-101
)。最后,启用选项列表依次转换为目标特定的 CXXFLAGS
值 addprefix
.
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 runN
tests in parallel withmake -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.