如何为调试和发布版本配置我的makefile?


175

我的项目有以下makefile,我想对其进行配置以进行发布和调试。在我的代码中,我有很多#ifdef DEBUG宏,因此只需设置此宏并将-g3 -gdwarf2标志添加到编译器即可。我怎样才能做到这一点?

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

为了澄清一下,当我说发布/调试版本时,我希望能够键入make并获取发布版本或make debug调试版本,而无需手动注释makefile中的内容。


12
注意!$(CC)=某些与CC =有所不同
levif 2012年

4
可执行目标违反了makefile的黄金法则:每个目标都应更新命名目标的文件,在您的情况下为“可执行”。
JesperE 2015年

3
^如果没有,则应声明它.PHONY
underscore_d

Answers:


192

您可以使用特定于目标的变量值。例:

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

请记住,在所有编译命令中都使用$(CXX)或$(CC)。

然后,“ make debug”将具有-DDEBUG和-g等额外标志,而“ make”则没有。

附带一提,您可以像其他帖子所建议的那样使Makefile更加简洁。


42
绝对不要在Makefile或BadThingsMayHappen(TM)中更改CXX或CC,这些文件包含要运行的可执行文件的路径和/或名称。CPPFLAGS,CXXFLAGS和CFLAGS可以达到这个目的。

11
该建议很差,因为它混合了调试和非调试目标文件,从而最终导致构建损坏。
Maxim Egorushkin

@MaximEgorushkin如何解决?我最近遇到了这个问题。我有一个调试可执行文件,已与发行版目标文件链接在一起。到目前为止,唯一的解决方案是声明调试并释放有害的伪音
MauriceRandomNumber

3
@MauriceRandomNumber将调试/发行版构建到其自己的文件夹中。示例:stackoverflow.com/a/48793058/412080
Maxim Egorushkin

43

在寻找类似问题时,经常会出现这个问题,因此我认为有必要实施一个完整的解决方案。特别是自从我(我会假设其他人)努力将所有各种答案拼凑在一起时。

下面是一个示例Makefile,它在单独的目录中支持多种构建类型。所示示例显示了调试和发布版本。

支持...

  • 特定项目的单独项目目录
  • 轻松选择默认目标版本
  • 静默的准备目标,以创建构建项目所需的目录
  • 特定于构建的编译器配置标志
  • GNU Make确定项目是否需要重建的自然方法
  • 模式规则,而不是过时的后缀规则

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

您如何修改此设置以允许在Makefile所在的目录以外的目录中构建源文件?
杰斐逊·哈德森

@JeffersonH​​udson如果源文件位于名为的目录中src,则将行修改SRCS = file1.c file2.c file3.c file4.c为read SRCS = src/file1.c src/file2.c src/file3.c src/file4.c
zero2cx

3
我不喜欢的是重复调试和发布的所有规则和变量。我有一个类似的Makefile,但是在扩展它时,我需要仔细复制粘贴每个新内容以进行调试和发布,然后仔细进行转换。
BeeOnRope

这应该是公认的答案。我希望我很久以前就看到过。
Michael Dorst,

42

如果通过配置发布/构建,您的意思是每个makefile仅需要一个配置,那么将CC和CFLAGS分离就可以了:

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

根据是否可以使用gnu makefile,可以使用conditional使它更高级,并从命令行对其进行控制:

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

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

然后使用:

make DEBUG=0
make DEBUG=1

如果您需要同时控制两个配置,我认为最好有一个构建目录和一个构建目录/ config。


18
我不知道自己是否在做奇怪的事情,但是为了使debug if语句ifeq (DEBUG, 1)对我有用(),DEBUG需要将该变量包装在括号中,如下所示:ifeq ($(DEBUG), 1)
shanet 2012年

25

请注意,您还可以同时简化Makefile:

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

现在,您不必在所有位置重复文件名。任何.l文件将通过flex和gcc传递,任何.y文件将通过bison和g ++传递,任何.cpp文件通过g ++传递。

只要列出您希望最终使用的.o文件,Make就会确定哪些规则可以满足需求。

作为记录:

  • $@ 目标文件的名称(冒号前的一个)

  • $< 第一个(或唯一)必备文件的名称(冒号之后的第一个)

  • $^ 所有必备文件的名称(以空格分隔)

  • $*词干(与%规则定义中的通配符匹配的位。


您在“用于记录”部分中,一项定义了两次,并具有不同的描述。根据gnu.org/software/make/manual/make.html#Automatic-Variables$^适用于所有必备文件。
格兰特·彼得斯

感谢您的授予-错字修复!(我检查了Makefile,看起来在那里正确使用了它,但输入了错误的说明。)
Stobor 2010年

2
我希望有更多这些简短的指南来编写相当小的Makefile,包括自动变量。
AzP 2011年

既有调试目标又有发行目标,而不必更改Makefile,并且可以根据自己的喜好选择默认值,这是很好的。

1
该解决方案具有以下问题:调试和发布输出文件在同一目录中混合在一起。如果它们不兼容,除非您每次在调试与不调试之间进行转换时都要小心进行清理,否则这将以怪异而奇妙的方式爆炸。即使它们兼容,它也不会完全清除您所期望的:如果您将项目构建为发布版本,然后使DEBUG = 1,它将仅重建其源已更改的文件,因此通常不会以这种方式获得“调试”构建。
BeeOnRope

3

你可以有一个变量

DEBUG = 0

然后您可以使用条件语句

  ifeq ($(DEBUG),1)

  else

  endif

2

完成前面的答案...您需要引用在命令中定义信息的变量...

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

1
有一个(现已删除?)答案(应该是对答案的评论),ifeq (DEBUG, 1)应注明为ifeq ($(DEBUG), 1)。我猜可能是在这里指您的答案。
基思M

0

您也可以在Makefile中添加一些简单的内容,例如

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

然后编译它进行调试

make DEBUG=1

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.