我有一个大型程序和库,这些程序和库都用C ++编写并使用make构建。在makefile中设置的大约十二个选项会变成预处理器指令,这些指令会更改实现(使用ifdef
等)。现在,当我在运行代码之前更改这些编译器选项时,会通过make clean
强制执行构建过程。我想设置系统,以便二进制文件的名称根据选项而变化。但是,我担心将来会错过一个或添加一个,而忘记更改名称,等等。是否有解决此问题的干净方法?
我考虑过的一些选择:
手动创建一个二进制名称,例如APP。#{OPT_1}。#{OPT_2}。#{OPT_3}。#{OPT_4},然后运行该名称
根据所有编译器标志(CXXFLAGS)创建一个哈希(例如SHA1),并将该哈希放入我的二进制文件名称中,例如APP。#{SHA1(CXXFLAGS)}。这具有将来可扩展的价值。
还有更好的方法/建议吗?
答案 0 :(得分:2)
还有更好的方法/建议吗?
如果我理解正确,那么您的GNU Make构建系统可以构建以下几种
您的可执行文件,由已定义(或未定义)的预处理器宏区分开
在编译命令中,具体取决于在Makefile中测试的条件
和/或传递给make
的参数上。而您希望能够建立
这些变体中的任何一个都独立存在,而无需make clean
来删除
先前构建的工件,很可能是其他构建的工件
变体。
这是构建系统的基本需求之一。常规解决方案不是
您正在考虑的一种-以某种方式将差异编码为
可执行文件。除非您用的名称执行相同的操作
链接到可执行文件的 object 文件。如果不这样做,那么什么时候
您从变体 X 切换到变体 Y ,变体- X 对象文件foo.o
不早于foo.cpp
的文件,无需重新编译,
即使应该用于变体- Y ,并且该变体- X foo.o
也会链接到
变体 Y 可执行文件,无论它叫什么。
常规解决方案是按变体区分编译器的 place 将输出目标文件,并相应地输出链接器所在的 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 使用它的 do 在幕后生成的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
的发行版本。
我们现在已经在不同的prog
子目录中构建了程序./bin[/...]
的四个版本
来自不同./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来构建或清理,
所有变体同时出现。如果未定义DEBUG
,则默认为RELEASE
,反之亦然。对于某些 valid 定义,如果没有选择有效的特征组合,则失败。
BTW,请注意,通常在make变量中分配 preprocessor 选项
CPPFLAGS
,用于C或C ++编译;分配了C compiler 选项
CFLAGS
中的选项,以及CXXFLAGS
中的C ++ compiler 选项。 GNU Make的内置
遵守这些约定的规则假定。
答案 1 :(得分:0)
我不确定仅使用二进制名称来分隔不同的构建配置是一个好主意。
>更改编译器选项是否仍会导致目标文件被踩踏,因为我认为它们的名称将与源文件相同,并且仍然有效地进行了完全重建?在我看来,这是资源外构建的主要候选人。配置您的构建脚本,以在源目录之外的单独目录中创建所有中间文件和输出文件。每套不同的构建选项集将使用不同的构建目录,也许根据您的建议,根据编译器标志的哈希值动态选择目录名称。
这将为您提供一个干净的源代码树,并且通过更改makefile / build脚本中的编译器选项,您可以将中间文件和输出文件更改到哪个目录。