Скомпилированный двоичный путь на основе параметров компилятора - PullRequest
0 голосов
/ 26 октября 2018

У меня есть большая программа и библиотека, написанная на C ++ и созданная с помощью make. Около дюжины опций, установленных в make-файле, превращаются в директивы препроцессора, которые изменяют реализацию (с ifdef s и т. Д.). Прямо сейчас я грубо форсирую свой процесс сборки, когда меняю эти параметры компилятора перед запуском кода. Я хотел бы настроить систему так, чтобы имя двоичного файла изменялось в зависимости от параметров. Но я беспокоюсь, что в будущем я пропущу или добавлю один и забуду сменить имя и т. Д. Есть ли чистый способ решения этой проблемы?

Некоторые варианты, которые я рассмотрел:

  1. вручную создайте двоичное имя, такое как APP. # {OPT_1}. # {OPT_2}. # {OPT_3}. # {OPT_4} при сборке, а затем запустите его

  2. создайте хеш (например, SHA1) из всех флагов компилятора (CXXFLAGS) и поместите этот хеш в имя моего бинарного файла, например, APP. # {SHA1 (CXXFLAGS)}. Это имеет значение расширяемости в будущем.

Есть ли лучшие подходы / рекомендации?

Ответы [ 2 ]

0 голосов
/ 27 октября 2018

Есть ли лучшие подходы / рекомендации?

Если я правильно понимаю, ваша система сборки GNU Make может собрать несколько вариантов ваш исполняемый файл, дифференцированный по макросам препроцессора, которые определены (или нет) в командах компиляции в зависимости от условий, которые тестируются в вашем Makefile и / или аргументы, которые вы передаете make. И вы хотите быть в состоянии построить любой из этих вариантов независимо, без необходимости make clean, чтобы удалить артефакты предыдущей сборки, которые вполне могли быть сборкой другого вариант.

Это одна из основных потребностей систем сборки. Традиционное решение не один, о котором вы думаете - как-то закодировать различия в имя исполняемый файл Это не сработает, если вы не сделаете то же самое с именами объект файлы, которые связаны с исполняемым файлом. Если нет, то когда вы переключаетесь с варианта X на вариант Y , вариант - X объектный файл foo.o который не старше foo.cpp, не нуждается в перекомпиляции, даже если должен для варианта- Y , и этот вариант- X foo.o будет связан с вариант Y исполняемый, независимо от того, как он называется.

Традиционное решение состоит в том, чтобы дифференцировать для каждого варианта место , где компилятор выведет объектные файлы и соответственно место , где компоновщик выводит исполняемый файл Без сомнения, все 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

без лишних слов, и варианты могут быть построены независимо.

Это просто реализовать эту схему в make-файле. Большинство IDE использующие его do реализуют его в make-файлах, которые они генерируют за кулисами. И также просто расширить схему на большее количество вариантов, чем просто debug и release (хотя, какие бы варианты вы ни выбрали, вам наверняка захочется debug и выпуск вариантов этих вариантов).

Вот иллюстрация для игрушечной программы, которую мы хотим встроить в любую из варианты, которые мы получаем для комбинаций двух свойств сборки, которые мы назовем 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. Так что чисто для целей демонстрации мы всегда будем компилировать и связывать с этой опцией. Вот make-файл, который использует один из способов дифференциации всех вариантов: объектные файлы скомпилированы в ./obj[/trait...]; исполняемые файлы связаны в ./bin[/trait...]:

Makefile

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

В этом упражнении вы можете подумать об уточнении make-файла, чтобы вы могли собрать или очистить, все варианты одновременно. Или по умолчанию DEBUG, если RELEASE не определено, или наоборот. Или потерпеть неудачу, если не выбрана допустимая комбинация признаков, для некоторого определения valid .

Кстати, обратите внимание, что опции препроцессора обычно назначаются в переменной make CPPFLAGS, для компиляции C или C ++; C компилятор опции назначены в CFLAGS и C ++ , компилятор , опции в CXXFLAGS. GNU Make встроен правила предполагают , что вы следуете этим соглашениям.

0 голосов
/ 27 октября 2018

Я не уверен, что будет хорошей идеей просто использовать двоичное имя для разделения различных конфигураций сборки.Не приведет ли изменение параметров компилятора к тому, что объектные файлы будут растоптаны, поскольку я предполагаю, что они будут называться так же, как исходные файлы, и в любом случае это все равно будет полная перестройка?

Этовыглядит для меня как главный кандидат для сборок вне исходного кода.Сконфигурируйте ваши сценарии сборки для создания всех промежуточных и выходных файлов в отдельном каталоге за пределами исходного каталога.Каждый отдельный набор параметров сборки будет использовать другой каталог сборки, возможно, выбирая имя каталога динамически на основе хэша флагов компилятора, как вы предложили.

Это даст вам чистое дерево исходных кодов и путем измененияОпция компилятора в скриптах makefile / build позволяет изменить каталог, в который будут помещены промежуточные и выходные файлы.

...