如何配置我的 makefile 以进行调试和发布版本?

Posted

技术标签:

【中文标题】如何配置我的 makefile 以进行调试和发布版本?【英文标题】:How can I configure my makefile for debug and release builds? 【发布时间】:2010-11-07 23:12:31 【问题描述】:

我的项目有以下 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 中的内容。

【问题讨论】:

注意! $(CC) = 某事不同于 CC = 某事 可执行目标违反了makefile的黄金法则:每个目标都应该更新命名目标的文件,在你的情况下是“可执行的”。 ^ 如果没有,则应声明为.PHONY 【参考方案1】:

您可以使用Target-specific Variable Values。示例:

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 更加简洁。

【讨论】:

您不应该更改 Makefile 或 BadThingsMayHappen (TM) 中的 CXX 或 CC,它们包含要运行的可执行文件的路径和/或名称。 CPPFLAGS、CXXFLAGS 和 CFLAGS 用于此目的。 这个建议很糟糕,因为它混合了调试和非调试目标文件,因此最终构建损坏。 @MaximEgorushkin 如何解决这个问题?我最近遇到了这个问题。我有一个调试可执行版本,它与发布目标文件链接。到目前为止唯一的解决方案是声明调试并发布目标虚假 @MauriceRandomNumber 将调试/发布版本构建到自己的文件夹中。示例:***.com/a/48793058/412080 我必须将 -gdwarf2 更改为 -gdwarf-2 才能使其与 clang v12.0.0 一起使用【参考方案2】:

这个问题在搜索类似问题时经常出现,所以我觉得有必要提供一个完全实施的解决方案。特别是因为我(我会假设其他人)一直在努力将所有不同的答案拼凑在一起。

下面是一个示例 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 行修改为SRCS = src/file1.c src/file2.c src/file3.c src/file4.c 我不喜欢的是所有规则和变量的重复,用于调试和发布。我有一个类似的 Makefile,但是在扩展它时,我需要小心地复制粘贴每个新的东西以进行调试和发布,并小心地转换它。 这应该是公认的答案。我希望我很久以前就看到了。【参考方案3】:

如果通过配置发布/构建,您的意思是每个 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。

【讨论】:

我不知道我是否在做一些奇怪的事情,但是为了让调试 if 语句为我工作 (ifeq (DEBUG, 1)),DEBUG 变量需要用括号括起来,如下所示: ifeq ($(DEBUG), 1).【参考方案4】:

请注意,您还可以同时使您的 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 就会确定哪些规则可以满足需求...

记录在案:

$@目标文件名(冒号前)

$&lt;第一个(或唯一一个)必备文件的名称(冒号后的第一个)

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

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

【讨论】:

您的“记录”部分有一个项目定义了两次,但描述不同。根据gnu.org/software/make/manual/make.html#Automatic-Variables,$^ 是所有必备文件。 感谢您的授予 - 已修复错字! (我检查了 Makefile,看来我在那里正确使用了它,但拼错了解释。) 我希望有更多这样的简短指南来编写相当小的 Makefile,包括自动变量。 无需更改 Makefile 即可同时拥有调试和发布目标,并且能够根据自己的喜好选择默认值,这真是太好了。 这个方案存在debug和release输出文件混在同一个目录下的问题。如果它们不兼容,这将以奇怪而奇妙的方式爆炸,除非您每次在调试和非调试之间切换时都小心地进行清理。即使它们是兼容的,如果没有清理它也不会做你期望的事情:如果你将项目构建为发布,然后使 DEBUG=1,它只会重建源已更改的文件,所以你通常不会以这种方式获得“调试”版本。【参考方案5】:

你可以有一个变量

DEBUG = 0

那么你可以使用条件语句

  ifeq ($(DEBUG),1)

  else

  endif

【讨论】:

【参考方案6】:

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

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

【讨论】:

有一个(现在已删除?)答案(应该是对答案的评论)指出ifeq (DEBUG, 1) 应该是ifeq ($(DEBUG), 1)。我猜它可能在这里指的是您的答案。【参考方案7】:

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

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

然后编译调试

make DEBUG=1

【讨论】:

以上是关于如何配置我的 makefile 以进行调试和发布版本?的主要内容,如果未能解决你的问题,请参考以下文章

从 IDE 调用 Makefile 以进行调试/发布

在 C 中使用 makefile 标志进行调试

如何迭代 Ninject StandardKernel 的已配置绑定以进行调试?

如何调试makefile变量

如何调试makefile变量

在 Visual Studio Code 中使用 Makefile 调试现有项目