ChatGPT解决这个技术问题 Extra ChatGPT

How can I configure my makefile for debug and release builds?

I have the following makefile for my project, and I'd like to configure it for release and debug builds. In my code, I have lots of #ifdef DEBUG macros in place, so it's simply a matter of setting this macro and adding the -g3 -gdwarf2 flags to the compilers. How can I do this?

$(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

Just to clarify, when I say release/debug builds, I want to be able to just type make and get a release build or make debug and get a debug build, without manually commenting out things in the makefile.

Attention! $(CC) = something is different than CC = something
The executable target violates the golden rule of makefiles: every target should update the file naming the target, in your case "executable".
^ And if it doesn't, it should be declared .PHONY

C
Chnossos

You can use Target-specific Variable Values. Example:

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

Remember to use $(CXX) or $(CC) in all your compile commands.

Then, 'make debug' will have extra flags like -DDEBUG and -g where as 'make' will not.

On a side note, you can make your Makefile a lot more concise like other posts had suggested.


You should never change CXX or CC within a Makefile or BadThingsMayHappen (TM), those contain the path and/or name of the executables to run. CPPFLAGS, CXXFLAGS and CFLAGS serve this purpose.
This advice is poor because it mixes debug and non-debug object files, so that one ends up with a corrupted build.
@MaximEgorushkin how to fix that? I came across this problem recently. I have a debug executable build, that was linked with release object files. Only solution so far was to declare debug and release targest phony
@MauriceRandomNumber Build debug/release into its own folders. Example: stackoverflow.com/a/48793058/412080
I had to change -gdwarf2 to -gdwarf-2 to get it to work with clang v12.0.0
f
ffhaddad

This question has appeared often when searching for a similar problem, so I feel a fully implemented solution is warranted. Especially since I (and I would assume others) have struggled piecing all the various answers together.

Below is a sample Makefile which supports multiple build types in separate directories. The example illustrated shows debug and release builds.

Supports ...

separate project directories for specific builds

easy selection of a default target build

silent prep target to create directories needed for building the project

build-specific compiler configuration flags

GNU Make's natural method of determining if project requires a rebuild

pattern rules rather than the obsolete suffix rules

#
# 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)

How do you modify this to allow building of source files in a directory other than the one in which Makefile resides?
@JeffersonHudson If source files are in a directory named src, then modify the line SRCS = file1.c file2.c file3.c file4.c to read SRCS = src/file1.c src/file2.c src/file3.c src/file4.c.
The thing I don't like is the duplication of all the rules and variables for debug and release. I have a similar Makefile but when extending it I need to carefully copy paste each new thing for debug and release and carefully convert it.
This should be the accepted answer. I wish I'd seen this a long time ago.
C
CharlesB

If by configure release/build, you mean you only need one config per makefile, then it is simply a matter and decoupling CC and CFLAGS:

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

Depending on whether you can use gnu makefile, you can use conditional to make this a bit fancier, and control it from the command line:

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

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

and then use:

make DEBUG=0
make DEBUG=1

If you need to control both configurations at the same time, I think it is better to have build directories, and one build directory / config.


I don't know if I'm doing something strange, but to get the debug if statement to work (ifeq (DEBUG, 1)) for me, the DEBUG variable needed wrapped in parentheses like so: ifeq ($(DEBUG), 1).
S
Stobor

Note that you can also make your Makefile simpler, at the same time:

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

Now you don't have to repeat filenames all over the place. Any .l files will get passed through flex and gcc, any .y files will get passed through bison and g++, and any .cpp files through just g++.

Just list the .o files you expect to end up with, and Make will do the work of figuring out which rules can satisfy the needs...

for the record:

$@ The name of the target file (the one before the colon)

$< The name of the first (or only) prerequisite file (the first one after the colon)

$^ The names of all the prerequisite files (space separated)

$* The stem (the bit which matches the % wildcard in the rule definition.


You're "for the record" section has one item defined twice with different descriptions. According to gnu.org/software/make/manual/make.html#Automatic-Variables, $^ is for all of the prerequisite files.
Thanks for that Grant - typo fixed! (I checked over the Makefile, and it appears I used it correctly there, but typoed the explanation.)
I wish there were more of these short guides to writing a reasonably small Makefiles, including the automatic variables.
It is nice to have both a debug and release target without having to change the Makefile, and the ability to pick the default based on your own preference.
This solution has the problem that the debug and release output files are mixed together in the same directory. If they are not compatible, this will blow up in weird and wonderful ways unless you are careful to do a clean every time you change between debug and not. Even if they are compatible, it won't do what you expect without a clean: if you have the project built as release, and then make DEBUG=1, it will only rebuild files whose source has changed, so you won't generally get a "debug" build that way.
T
Tiberiu

you can have a variable

DEBUG = 0

then you can use a conditional statement

  ifeq ($(DEBUG),1)

  else

  endif

S
Stobor

Completing the answers from earlier... You need to reference the variables you define info in your commands...

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

There's a (now deleted?) Answer (which should have been a Comment on an Answer) that noted ifeq (DEBUG, 1) should be ifeq ($(DEBUG), 1). I'm guessing it may have been referring to your Answer here.
M
Manolete

You could also add something simple to your Makefile such as

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

Then compile it for debugging

make DEBUG=1