Wie kann ich mein Makefile für Debug- und Release-Builds konfigurieren?

175

Ich habe das folgende Makefile für mein Projekt und möchte es für Release- und Debug-Builds konfigurieren. In meinem Code sind viele #ifdef DEBUGMakros vorhanden. Es geht also nur darum, dieses Makro zu setzen und -g3 -gdwarf2den Compilern die Flags hinzuzufügen . Wie kann ich das machen?

$(CC) = g++ -g3 -gdwarf2
$(cc) = gcc -g3 -gdwarf2

all: executable

executable: CommandParser.tab.o CommandParser.yy.o Command.o
    g++ -g -o output CommandParser.yy.o CommandParser.tab.o Command.o -lfl

CommandParser.yy.o: CommandParser.l 
    flex -o CommandParser.yy.c CommandParser.l
    gcc -g -c CommandParser.yy.c

CommandParser.tab.o: CommandParser.y
    bison -d CommandParser.y
    g++ -g -c CommandParser.tab.c

Command.o: Command.cpp
    g++ -g -c Command.cpp

clean:
    rm -f CommandParser.tab.* CommandParser.yy.* output *.o

Um zu verdeutlichen, wenn ich Release- / Debug-Builds sage, möchte ich in der Lage sein, einfach makeeinen Release-Build oder make debugeinen Debug-Build einzugeben und abzurufen, ohne die Dinge im Makefile manuell auskommentieren zu müssen.

Samoz
quelle
12
Beachtung! $ (CC) = etwas ist anders als CC = etwas
Levif
4
Das ausführbare Ziel verstößt gegen die goldene Regel von Makefiles: Jedes Ziel sollte die Datei mit dem Namen des Ziels aktualisieren, in Ihrem Fall "ausführbar".
JesperE
3
^ Und wenn nicht, sollte es deklariert werden.PHONY
underscore_d

Antworten:

192

Sie können zielspezifische Variablenwerte verwenden . Beispiel:

CXXFLAGS = -g3 -gdwarf2
CCFLAGS = -g3 -gdwarf2

all: executable

debug: CXXFLAGS += -DDEBUG -g
debug: CCFLAGS += -DDEBUG -g
debug: executable

executable: CommandParser.tab.o CommandParser.yy.o Command.o
    $(CXX) -o output CommandParser.yy.o CommandParser.tab.o Command.o -lfl

CommandParser.yy.o: CommandParser.l 
    flex -o CommandParser.yy.c CommandParser.l
    $(CC) -c CommandParser.yy.c

Denken Sie daran, in all Ihren Kompilierungsbefehlen $ (CXX) oder $ (CC) zu verwenden.

Dann hat 'make debug' zusätzliche Flags wie -DDEBUG und -g, während 'make' dies nicht tut.

Nebenbei bemerkt, Sie können Ihr Makefile viel prägnanter gestalten, als andere Beiträge vorgeschlagen hatten.

David Lin
quelle
42
Sie sollten CXX oder CC niemals in einem Makefile oder BadThingsMayHappen (TM) ändern. Diese enthalten den Pfad und / oder den Namen der ausführbaren Dateien, die ausgeführt werden sollen. CPPFLAGS, CXXFLAGS und CFLAGS dienen diesem Zweck.
11
Dieser Rat ist schlecht, da er Debug- und Nicht-Debug-Objektdateien mischt, so dass ein beschädigter Build entsteht.
Maxim Egorushkin
@ MaximEgorushkin wie man das behebt? Ich bin kürzlich auf dieses Problem gestoßen. Ich habe einen ausführbaren Debug-Build, der mit Release-Objektdateien verknüpft wurde. Bisher bestand die einzige Lösung darin, das Debuggen zu deklarieren und die größte
Fälschung
3
@MauriceRandomNumber Erstellen Sie Debugging / Release in eigenen Ordnern. Beispiel: stackoverflow.com/a/48793058/412080
Maxim Egorushkin
43

Diese Frage ist häufig bei der Suche nach einem ähnlichen Problem aufgetreten, daher halte ich eine vollständig implementierte Lösung für gerechtfertigt. Zumal ich (und ich würde andere annehmen) Schwierigkeiten hatte, all die verschiedenen Antworten zusammenzusetzen.

Unten finden Sie ein Beispiel für ein Makefile, das mehrere Build-Typen in separaten Verzeichnissen unterstützt. Das abgebildete Beispiel zeigt Debug- und Release-Builds.

Unterstützt ...

  • separate Projektverzeichnisse für bestimmte Builds
  • Einfache Auswahl eines Standard-Ziel-Builds
  • stilles Vorbereitungsziel zum Erstellen von Verzeichnissen, die zum Erstellen des Projekts benötigt werden
  • Build-spezifische Compiler-Konfigurationsflags
  • Die natürliche Methode von GNU Make, um festzustellen, ob ein Projekt neu erstellt werden muss
  • Musterregeln anstelle der veralteten Suffixregeln

#
# Compiler flags
#
CC     = gcc
CFLAGS = -Wall -Werror -Wextra

#
# Project files
#
SRCS = file1.c file2.c file3.c file4.c
OBJS = $(SRCS:.c=.o)
EXE  = exefile

#
# Debug build settings
#
DBGDIR = debug
DBGEXE = $(DBGDIR)/$(EXE)
DBGOBJS = $(addprefix $(DBGDIR)/, $(OBJS))
DBGCFLAGS = -g -O0 -DDEBUG

#
# Release build settings
#
RELDIR = release
RELEXE = $(RELDIR)/$(EXE)
RELOBJS = $(addprefix $(RELDIR)/, $(OBJS))
RELCFLAGS = -O3 -DNDEBUG

.PHONY: all clean debug prep release remake

# Default build
all: prep release

#
# Debug rules
#
debug: $(DBGEXE)

$(DBGEXE): $(DBGOBJS)
    $(CC) $(CFLAGS) $(DBGCFLAGS) -o $(DBGEXE) $^

$(DBGDIR)/%.o: %.c
    $(CC) -c $(CFLAGS) $(DBGCFLAGS) -o $@ $<

#
# Release rules
#
release: $(RELEXE)

$(RELEXE): $(RELOBJS)
    $(CC) $(CFLAGS) $(RELCFLAGS) -o $(RELEXE) $^

$(RELDIR)/%.o: %.c
    $(CC) -c $(CFLAGS) $(RELCFLAGS) -o $@ $<

#
# Other rules
#
prep:
    @mkdir -p $(DBGDIR) $(RELDIR)

remake: clean all

clean:
    rm -f $(RELEXE) $(RELOBJS) $(DBGEXE) $(DBGOBJS)
ffhaddad
quelle
Wie können Sie dies ändern, um das Erstellen von Quelldateien in einem anderen Verzeichnis als dem zu ermöglichen, in dem sich Makefile befindet?
Jefferson Hudson
@JeffersonHudson Wenn sich Quelldateien in einem Verzeichnis mit dem Namen befinden src, ändern Sie die SRCS = file1.c file2.c file3.c file4.czu lesende Zeile SRCS = src/file1.c src/file2.c src/file3.c src/file4.c.
zero2cx
3
Was mir nicht gefällt, ist das Duplizieren aller Regeln und Variablen zum Debuggen und Freigeben. Ich habe ein ähnliches Makefile, aber wenn ich es erweitere, muss ich jedes neue Objekt zum Debuggen und Freigeben sorgfältig kopieren, einfügen und sorgfältig konvertieren.
BeeOnRope
Dies sollte die akzeptierte Antwort sein. Ich wünschte, ich hätte das schon vor langer Zeit gesehen.
Michael Dorst
42

Wenn Sie mit configure release / build nur eine Konfiguration pro Makefile benötigen, ist dies einfach eine Frage der Entkopplung von CC und CFLAGS:

CFLAGS=-DDEBUG
#CFLAGS=-O2 -DNDEBUG
CC=g++ -g3 -gdwarf2 $(CFLAGS)

Abhängig davon, ob Sie gnu makefile verwenden können, können Sie bedingt verwenden, um dies ein bisschen schicker zu machen, und es über die Befehlszeile steuern:

DEBUG ?= 1
ifeq ($(DEBUG), 1)
    CFLAGS =-DDEBUG
else
    CFLAGS=-DNDEBUG
endif

.o: .c
    $(CC) -c $< -o $@ $(CFLAGS)

und dann benutze:

make DEBUG=0
make DEBUG=1

Wenn Sie beide Konfigurationen gleichzeitig steuern müssen, ist es meiner Meinung nach besser, Build-Verzeichnisse und ein Build-Verzeichnis / Konfiguration zu haben.

David Cournapeau
quelle
18
Ich weiß nicht, ob ich etwas Seltsames mache, aber um die Debug-if-Anweisung ifeq (DEBUG, 1)für mich zum Laufen zu bringen ( ), muss die DEBUGVariable in Klammern gesetzt werden : ifeq ($(DEBUG), 1).
shanet
25

Beachten Sie, dass Sie gleichzeitig Ihr Makefile vereinfachen können:

DEBUG ?= 1
ifeq (DEBUG, 1)
    CFLAGS =-g3 -gdwarf2 -DDEBUG
else
    CFLAGS=-DNDEBUG
endif

CXX = g++ $(CFLAGS)
CC = gcc $(CFLAGS)

EXECUTABLE = output
OBJECTS = CommandParser.tab.o CommandParser.yy.o Command.o
LIBRARIES = -lfl

all: $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
    $(CXX) -o $@ $^ $(LIBRARIES)

%.yy.o: %.l 
    flex -o $*.yy.c $<
    $(CC) -c $*.yy.c

%.tab.o: %.y
    bison -d $<
    $(CXX) -c $*.tab.c

%.o: %.cpp
    $(CXX) -c $<

clean:
    rm -f $(EXECUTABLE) $(OBJECTS) *.yy.c *.tab.c

Jetzt müssen Sie Dateinamen nicht mehr überall wiederholen. Alle .l-Dateien werden über flex und gcc übertragen, alle .y-Dateien werden über Bison und g ++ und alle .cpp-Dateien nur über g ++ weitergeleitet.

Listen Sie einfach die .o-Dateien auf, mit denen Sie voraussichtlich enden werden, und Make wird herausfinden, welche Regeln die Anforderungen erfüllen können ...

für die Aufzeichnung:

  • $@ Der Name der Zieldatei (die vor dem Doppelpunkt)

  • $< Der Name der ersten (oder einzigen) vorausgesetzten Datei (die erste nach dem Doppelpunkt)

  • $^ Die Namen aller erforderlichen Dateien (durch Leerzeichen getrennt)

  • $*Der Stamm (das Bit, das mit dem %Platzhalter in der Regeldefinition übereinstimmt .

Stobor
quelle
In Ihrem Abschnitt "Für den Datensatz" ist ein Element zweimal mit unterschiedlichen Beschreibungen definiert. Nach gnu.org/software/make/manual/make.html#Automatic-Variables , $^ist für alle die erforderlichen Dateien.
Grant Peters
Danke für diesen Grant - Tippfehler behoben! (Ich habe das Makefile überprüft und es scheint, dass ich es dort korrekt verwendet habe, aber die Erklärung getippt habe.)
Stobor
2
Ich wünschte, es gäbe mehr dieser kurzen Anleitungen zum Schreiben einigermaßen kleiner Makefiles, einschließlich der automatischen Variablen.
AzP
Es ist schön, sowohl ein Debug- als auch ein Release-Ziel zu haben, ohne das Makefile ändern zu müssen, und die Möglichkeit, den Standard nach Ihren Wünschen auszuwählen.
1
Diese Lösung hat das Problem, dass die Debug- und Release-Ausgabedateien im selben Verzeichnis gemischt werden. Wenn sie nicht kompatibel sind, wird dies auf seltsame und wunderbare Weise explodieren, es sei denn, Sie müssen jedes Mal, wenn Sie zwischen Debuggen und Nicht-Debuggen wechseln, eine Bereinigung durchführen. Selbst wenn sie kompatibel sind, wird es ohne eine Bereinigung nicht das tun, was Sie erwarten: Wenn Sie das Projekt als Release erstellt haben und dann DEBUG = 1 machen, werden nur Dateien neu erstellt, deren Quelle sich geändert hat, sodass Sie dies im Allgemeinen nicht tun Holen Sie sich einen "Debug" Build auf diese Weise.
BeeOnRope
3

Sie können eine Variable haben

DEBUG = 0

dann können Sie eine bedingte Anweisung verwenden

  ifeq ($(DEBUG),1)

  else

  endif
Tiberiu
quelle
2

Vervollständigen der Antworten von früher ... Sie müssen auf die Variablen verweisen, die Sie in Ihren Befehlen definieren ...

DEBUG ?= 1
ifeq (DEBUG, 1)
    CFLAGS =-g3 -gdwarf2 -DDEBUG
else
    CFLAGS=-DNDEBUG
endif

CXX = g++ $(CFLAGS)
CC = gcc $(CFLAGS)

all: executable

executable: CommandParser.tab.o CommandParser.yy.o Command.o
    $(CXX) -o output CommandParser.yy.o CommandParser.tab.o Command.o -lfl

CommandParser.yy.o: CommandParser.l 
    flex -o CommandParser.yy.c CommandParser.l
    $(CC) -c CommandParser.yy.c

CommandParser.tab.o: CommandParser.y
    bison -d CommandParser.y
    $(CXX) -c CommandParser.tab.c

Command.o: Command.cpp
    $(CXX) -c Command.cpp

clean:
    rm -f CommandParser.tab.* CommandParser.yy.* output *.o
Stobor
quelle
1
Es gibt eine (jetzt gelöschte?) Antwort (die ein Kommentar zu einer Antwort sein sollte), die notiert ifeq (DEBUG, 1)werden sollte ifeq ($(DEBUG), 1). Ich vermute, es hat sich hier auf Ihre Antwort bezogen.
Keith M
0

Sie können Ihrem Makefile auch etwas Einfaches hinzufügen, z

ifeq ($(DEBUG),1)
   OPTS = -g
endif

Kompilieren Sie es dann zum Debuggen

make DEBUG=1

Manolete
quelle