小编典典

如何制作一个简单的 C++ Makefile

all

我们需要使用 Makefile 来为我们的项目整合所有内容,但我们的教授从未向我们展示过如何去做。

我只有 一个 文件,a3driver.cpp.
驱动程序从一个位置导入一个类,"/user/cse232/Examples/example32.sequence.cpp".

就是这样。其他所有内容都包含在.cpp.

我将如何制作一个简单的 Makefile 来创建一个名为的可执行文件a3a.exe


阅读 90

收藏
2022-04-11

共1个答案

小编典典

由于这是针对 Unix 的,因此可执行文件没有任何扩展名。

需要注意的一点是,它root-config是一个提供正确编译和链接标志的实用程序;以及用于针对 root
构建应用程序的正确库。这只是与本文档的原始受众相关的一个细节。

让我成为宝贝

或者你永远不会忘记你第一次被制造

一个关于make的介绍性讨论,以及如何编写一个简单的makefile

什么是制作? 我为什么要关心?

名为Make的工具是一个构建依赖管理器。也就是说,它负责了解需要以什么顺序执行哪些命令,以便从源文件、目标文件、库、头文件等的集合中获取您的软件项目——其中一些可能已经改变最近——并将它们变成正确的最新版本的程序。

实际上,您也可以将 Make 用于其他事情,但我不打算谈论这个。

一个简单的 Makefile

假设您有一个目录,其中包含: ,tool tool.cc tool.o support.cc support.hh
support.o依赖于root并且应该被编译成一个tool名为tool编译程序。

要自己做,你可以

  1. 检查是否support.ccsupport.hh比 新support.o,如果是,请运行类似的命令

    g++ -g -c -pthread -I/sw/include/root support.cc
    
  2. 检查其中一个support.hhtool.cc是否比 更新tool.o,如果是,则运行类似的命令

    g++ -g  -c -pthread -I/sw/include/root tool.cc
    
  3. 检查是否tool.o比 更新tool,如果是,则运行类似的命令

    g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
    

    -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
    -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

呸!多么麻烦!有很多事情要记住,也有很多犯错的机会。(顺便说一句——这里展示的命令行的细节取决于我们的软件环境。这些在我的电脑上工作。)

当然,您可以每次都运行所有三个命令。这会起作用,但它不能很好地扩展到大量软件(比如在我的 MacBook 上从头开始编译需要 15 分钟以上的 DOGS)。

相反,您可以编写一个文件,makefile如下所示:

tool: tool.o support.o
    g++ -g -o tool tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
        -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
        -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

tool.o: tool.cc support.hh
    g++ -g  -c -pthread -I/sw/include/root tool.cc

support.o: support.hh support.cc
    g++ -g -c -pthread -I/sw/include/root support.cc

只需make在命令行中输入。它将自动执行上面显示的三个步骤。

此处未缩进的行具有 “目标:依赖项” 的形式,并告诉
Make,如果任何依赖项比目标更新,则应运行关联的命令(缩进行)。也就是说,依赖行描述了需要重建以适应各种文件中的更改的逻辑。如果support.cc发生更改,则意味着support.o必须重建,但tool.o可以不理会。何时必须重建support.o更改。tool

与每个依赖行关联的命令都用一个选项卡(见下文)设置,应该修改目标(或至少触摸它以更新修改时间)。

变量、内置规则和其他好东西

在这一点上,我们的 makefile 只是记住了需要做的工作,但我们仍然必须弄清楚并完整地键入每个需要的命令。不一定非要如此:Make
是一种强大的语言,它具有变量、文本操作函数和大量内置规则,可以让我们更轻松地完成此操作。

制作变量

访问 make 变量的语法是$(VAR).

分配给 Make 变量的语法是:(VAR = A text value of some kindVAR := A different text value but ignore this for the moment)。

您可以在规则中使用变量,例如我们的 makefile 的改进版本:

CPPFLAGS=-g -pthread -I/sw/include/root
LDFLAGS=-g
LDLIBS=-L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
       -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz \
       -Wl,-framework,CoreServices -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root \
       -lm -ldl

tool: tool.o support.o
    g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS)

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

这更具可读性,但仍然需要大量输入

制作函数

GNU make 支持从文件系统或系统上的其他命令访问信息的各种功能。在这种情况下,我们感兴趣的是$(shell ...)哪个扩展为参数的输出,哪个替换文本中$(subst opat,npat,text)的所有实例。opat``npat

利用这一点,我们可以:

CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

tool: $(OBJS)
    g++ $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

这更容易输入并且更具可读性。

请注意

  1. 我们仍在明确说明每个目标文件和最终可执行文件的依赖关系
  2. 我们必须为两个源文件显式键入编译规则

隐式规则和模式规则

我们通常希望所有 C++ 源文件都应该以相同的方式处理,Make 提供了三种方式来说明这一点:

  1. 后缀规则(在 GNU make 中被认为已过时,但为了向后兼容而保留)
  2. 隐含规则
  3. 模式规则

隐式规则是内置的,下面将讨论一些规则。模式规则以如下形式指定

%.o: %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c $<

这意味着目标文件是通过运行显示的命令从 C 源文件生成的,其中“自动”变量$<扩展为第一个依赖项的名称。

内置规则

Make 有很多内置规则,这意味着很多时候,一个项目可以通过一个非常简单的 makefile 来编译,确实如此。

C 源文件的 GNU make 内置规则就是上面展示的规则。类似地,我们使用类似$(CXX) -c $(CPPFLAGS) $(CFLAGS).

单个目标文件使用 链接$(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS),但这在我们的例子中不起作用,因为我们想要链接多个目标文件。

内置规则使用的变量

内置规则使用一组标准变量,允许您指定本地环境信息(例如在哪里可以找到 ROOT 包含文件),而无需重写所有规则。我们最感兴趣的是:

  • CC-- 要使用的 C 编译器
  • CXX-- 要使用的 C++ 编译器
  • LD-- 要使用的链接器
  • CFLAGS-- C 源文件的编译标志
  • CXXFLAGS-- C++ 源文件的编译标志
  • CPPFLAGS-- C 和 C++ 使用的 c 预处理器的标志(通常包括在命令行上定义的文件路径和符号)
  • LDFLAGS-- 链接器标志
  • LDLIBS-- 要链接的库

一个基本的 Makefile

通过利用内置规则,我们可以将 makefile 简化为:

CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

tool.o: tool.cc support.hh

support.o: support.hh support.cc

clean:
    $(RM) $(OBJS)

distclean: clean
    $(RM) tool

我们还添加了几个执行特殊操作(如清理源目录)的标准目标。

请注意,当在没有参数的情况下调用 make 时,它​​使用在文件中找到的第一个目标(在本例中为全部),但您也可以将目标命名为 get,这就是make clean在这种情况下删除目标文件的原因。

我们仍然对所有依赖项进行了硬编码。

一些神秘的改进

CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

depend: .depend

.depend: $(SRCS)
    $(RM) ./.depend
    $(CXX) $(CPPFLAGS) -MM $^>>./.depend;

clean:
    $(RM) $(OBJS)

distclean: clean
    $(RM) *~ .depend

include .depend

请注意

  1. 源文件不再有任何依赖行!?!
  2. .depend 和depend 有一些奇怪的魔法
  3. 如果你这样做了,make那么ls -A你会看到一个名为的文件.depend,其中包含看起来像 make 依赖行的东西

其他阅读

了解错误和历史记录

Make 的输入语言对空格敏感。特别是, 依赖项之后的操作行必须以 tab 开头
。但是一系列空格看起来是一样的(确实有些编辑器会默默地将制表符转换为空格,反之亦然),这会导致 Make
文件看起来正确但仍然无法工作。这在早期被确定为一个错误,但是(故事是这样的)它没有被修复,因为已经有
10 个用户。

(这是从我为物理研究生写的 wiki 帖子中复制的。)

2022-04-11