基于编译器选项的已编译二进制路径

时间:2018-10-26 14:59:04

标签: c++ makefile dependencies

我有一个大型程序和库,这些程序和库都用C ++编写并使用make构建。在makefile中设置的大约十二个选项会变成预处理器指令,这些指令会更改实现(使用ifdef等)。现在,当我在运行代码之前更改这些编译器选项时,会通过make clean强制执行构建过程。我想设置系统,以便二进制文件的名称根据选项而变化。但是,我担心将来会错过一个或添加一个,而忘记更改名称,等等。是否有解决此问题的干净方法?

我考虑过的一些选择:

  1. 手动创建一个二进制名称,例如APP。#{OPT_1}。#{OPT_2}。#{OPT_3}。#{OPT_4},然后运行该名称

  2. 根据所有编译器标志(CXXFLAGS)创建一个哈希(例如SHA1),并将该哈希放入我的二进制文件名称中,例如APP。#{SHA1(CXXFLAGS)}。这具有将来可扩展的价值。

还有更好的方法/建议吗?

2 个答案:

答案 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_ATRAIT_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脚本中的编译器选项,您可以将中间文件和输出文件更改到哪个目录。