Есть ли лучшие подходы / рекомендации?
Если я правильно понимаю, ваша система сборки 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 встроен
правила предполагают , что вы следуете этим соглашениям.