有更好的方法/建议吗?
如果我理解正确,您的 GNU Make 构建系统可以构建多个
您的可执行文件,由定义(或未定义)的预处理器宏区分
在编译命令中取决于您的 Makefile 中测试的条件
和/或传递给make 的参数。你希望能够建立
这些变体中的任何一个都是独立的,不需要make clean 来删除
先前构建的工件,很可能是不同构建的构建
变种。
这是构建系统的基本需求之一。传统的解决方案不是
您正在考虑的一个-以某种方式将差异编码为
可执行文件。无论如何这都行不通,除非你用名字做同样的事情
链接到可执行文件的 object 文件。如果你不这样做,那么什么时候
你从变体 X 切换到变体 Y,变体-X 对象文件foo.o
不早于foo.cpp,不需要重新编译,
即使 应该 用于变体-Y,并且变体-X foo.o 将链接到
变体 Y 可执行文件,不管它叫什么。
传统的解决方案是根据变体区分编译器所在的位置
将输出目标文件和相应的 place 链接器所在的位置
输出可执行文件。毫无疑问,您曾经使用过的所有 C/C++ IDE 都允许您
构建项目的 debug 变体或 release 变体,以及
他们将 debug 对象文件和可执行文件与 release 对象区分开来
通过在不同的子目录中生成文件和可执行文件
项目目录,例如
<projdir>/Debug/{obj|bin}
<projdir>/Release/{obj|bin}
或者也许:
<projdir>/obj/{debug|release}
<projdir>/bin/{debug|release}
这种方法自动编码目标文件或可执行文件的变体
进入其绝对路径名,例如
<projdir>/Debug/obj/foo.o
<projdir>/bin/release/prog
事不宜迟,变体可以独立构建。
在 makefile 中实现这个方案很简单。大多数 IDE
使用它的人做在他们在幕后生成的makefile中实现它。
并且将方案扩展到更多变体也很简单,而不仅仅是 debug
和 release (尽管你想要任何变体,你肯定会想要 debug
和 release 这些变体的变体)。
这是我们想要在任何
我们将调用的两个构建属性组合得到的变体
TRAIT_A 和 TRAIT_B:
| TRAIT_A | TRAIT_B |
|---------|---------|
| Y | Y |
|---------|---------|
| Y | N |
|---------|---------|
| N | Y |
|---------|---------|
| N | N |
并且我们希望能够在调试模式或发布中构建任何这些变体
模式。 TRAIT_{A|B} 可能直接映射到预处理器宏,或映射到
预处理器标志、编译器选项和/或链接选项的任意组合。
我们的程序 prog 仅由一个源文件构建而成:
main.cpp
#include <string>
#include <cstdlib>
int main(int atgc, char * argv[])
{
std::string cmd{"readelf -p .GCC.command.line "};
cmd += argv[0];
return system(cmd.c_str());
}
它所做的只是调用readelf 转储链接部分.GCC.command.line
在自己的可执行文件中。该链接部分仅在我们编译或
与 GCC 选项 -frecord-gcc-switches 链接。
因此,纯粹出于演示的目的,我们将始终编译并链接该选项。
这是一个makefile,它采用一种区分所有变体的方法:
目标文件编译在./obj[/trait...];可执行文件链接在
./bin[/trait...]:
生成文件
CXX = g++
CXXFLAGS := -frecord-gcc-switches
BINDIR := ./bin
OBJDIR := ./obj
ifdef RELEASE
ifdef DEBUG
$(error RELEASE and DEBUG are mutually exclusive)
endif
CPPFLAGS := -DNDEBUG
CXXFLAGS += -O3
BINDIR := $(BINDIR)/release
OBJDIR := $(OBJDIR)/release
endif
ifdef DEBUG
ifdef RELEASE
$(error RELEASE and DEBUG are mutually exclusive)
endif
CXXFLAGS += -O0 -g
BINDIR := $(BINDIR)/debug
OBJDIR := $(OBJDIR)/debug
endif
ifdef TRAIT_A
CPPFLAGS += -DTRAIT_A # or whatever
BINDIR := $(BINDIR)/TRAIT_A
OBJDIR := $(OBJDIR)/TRAIT_A
endif
ifdef TRAIT_B
CPPFLAGS += -DTRAIT_B # or whatever
BINDIR := $(BINDIR)/TRAIT_B
OBJDIR := $(OBJDIR)/TRAIT_B
endif
SRCS := main.cpp
OBJS := $(OBJDIR)/$(SRCS:.cpp=.o)
EXE := $(BINDIR)/prog
.PHONY: all clean
all: $(EXE)
$(EXE): $(OBJS) | $(BINDIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ $(LDFLAGS) $^ $(LIBS)
$(OBJDIR)/%.o: %.cpp | $(OBJDIR)
$(CXX) -c -o $@ $(CPPFLAGS) $(CXXFLAGS) $<
$(BINDIR) $(OBJDIR):
mkdir -p $@
clean:
$(RM) $(EXE) $(OBJS)
现在让我们在调试模式下构建两个变体,在调试模式下构建另外两个变体
释放模式,一个接一个
$ make DEBUG=1 TRAIT_A=1
mkdir -p obj/debug/TRAIT_A
g++ -c -o obj/debug/TRAIT_A/main.o -DTRAIT_A -frecord-gcc-switches -O0 -g main.cpp
mkdir -p bin/debug/TRAIT_A
g++ -DTRAIT_A -frecord-gcc-switches -O0 -g -o bin/debug/TRAIT_A/prog obj/debug/TRAIT_A/main.o
$ make DEBUG=1 TRAIT_B=1
mkdir -p obj/debug/TRAIT_B
g++ -c -o obj/debug/TRAIT_B/main.o -DTRAIT_B -frecord-gcc-switches -O0 -g main.cpp
mkdir -p bin/debug/TRAIT_B
g++ -DTRAIT_B -frecord-gcc-switches -O0 -g -o bin/debug/TRAIT_B/prog obj/debug/TRAIT_B/main.o
$ make RELEASE=1 TRAIT_A=1 TRAIT_B=1
mkdir -p obj/release/TRAIT_A/TRAIT_B
g++ -c -o obj/release/TRAIT_A/TRAIT_B/main.o -DNDEBUG -DTRAIT_A -DTRAIT_B -frecord-gcc-switches -O3 main.cpp
mkdir -p bin/release/TRAIT_A/TRAIT_B
g++ -DNDEBUG -DTRAIT_A -DTRAIT_B -frecord-gcc-switches -O3 -o bin/release/TRAIT_A/TRAIT_B/prog obj/release/TRAIT_A/TRAIT_B/main.o
$ make RELEASE=1
g++ -c -o obj/release/main.o -DNDEBUG -frecord-gcc-switches -O3 main.cpp
g++ -DNDEBUG -frecord-gcc-switches -O3 -o bin/release/prog obj/release/main.o
最后一个是发布版本,既没有 TRAIT_A 也没有 TRAIT_B。
我们现在在不同的./bin[/...] 子目录中构建了四个版本的程序prog
项目的,来自不同./obj[/...]子目录中的不同目标文件,
这些版本都将告诉我们它们的构建方式有何不同。按顺序运行
我们建造了它们:-
$ bin/debug/TRAIT_A/prog
String dump of section '.GCC.command.line':
[ 0] -imultiarch x86_64-linux-gnu
[ 1d] -D_GNU_SOURCE
[ 2b] -D TRAIT_A
[ 36] main.cpp
[ 3f] -mtune=generic
[ 4e] -march=x86-64
[ 5c] -auxbase-strip obj/debug/TRAIT_A/main.o
[ 84] -g
[ 87] -O0
[ 8b] -frecord-gcc-switches
[ a1] -fstack-protector-strong
[ ba] -Wformat
[ c3] -Wformat-security
$ bin/debug/TRAIT_B/prog
String dump of section '.GCC.command.line':
[ 0] -imultiarch x86_64-linux-gnu
[ 1d] -D_GNU_SOURCE
[ 2b] -D TRAIT_B
[ 36] main.cpp
[ 3f] -mtune=generic
[ 4e] -march=x86-64
[ 5c] -auxbase-strip obj/debug/TRAIT_B/main.o
[ 84] -g
[ 87] -O0
[ 8b] -frecord-gcc-switches
[ a1] -fstack-protector-strong
[ ba] -Wformat
[ c3] -Wformat-security
$ bin/release/TRAIT_A/TRAIT_B/prog
String dump of section '.GCC.command.line':
[ 0] -imultiarch x86_64-linux-gnu
[ 1d] -D_GNU_SOURCE
[ 2b] -D NDEBUG
[ 35] -D TRAIT_A
[ 40] -D TRAIT_B
[ 4b] main.cpp
[ 54] -mtune=generic
[ 63] -march=x86-64
[ 71] -auxbase-strip obj/release/TRAIT_A/TRAIT_B/main.o
[ a3] -O3
[ a7] -frecord-gcc-switches
[ bd] -fstack-protector-strong
[ d6] -Wformat
[ df] -Wformat-security
$ bin/release/prog
String dump of section '.GCC.command.line':
[ 0] -imultiarch x86_64-linux-gnu
[ 1d] -D_GNU_SOURCE
[ 2b] -D NDEBUG
[ 35] main.cpp
[ 3e] -mtune=generic
[ 4d] -march=x86-64
[ 5b] -auxbase-strip obj/release/main.o
[ 7d] -O3
[ 81] -frecord-gcc-switches
[ 97] -fstack-protector-strong
[ b0] -Wformat
[ b9] -Wformat-security
我们可以清理第一个:
$ make DEBUG=1 TRAIT_A=1 clean
rm -f ./bin/debug/TRAIT_A/prog ./obj/debug/TRAIT_A/main.o
最后一个:
$ make RELEASE=1 clean
rm -f ./bin/release/prog ./obj/release/main.o
第二个和第三个仍然存在并且是最新的:
$ make DEBUG=1 TRAIT_B=1
make: Nothing to be done for 'all'.
$ make RELEASE=1 TRAIT_A=1 TRAIT_B=1
make: Nothing to be done for 'all'.
对于这个练习,您可以考虑改进 makefile 以让您构建或清理,
同时所有变体。或者,如果未定义 RELEASE,则默认为 DEBUG,反之亦然。或者如果没有选择有效的特征组合,则失败,对于 valid 的某些定义。
顺便说一句,注意 preprocessor 选项通常在 make 变量中分配
CPPFLAGS,用于 C 或 C++ 编译;分配了 C 编译器 选项
在CFLAGS 和CXXFLAGS 中的C++ 编译器 选项中。 GNU Make 的内置
规则假定您遵循这些约定。