如何制作SIMPLE C ++ Makefile?

How to make a SIMPLE C++ Makefile?

我们需要使用Makefile将所有内容整合到我们的项目中,但我们的教授从未向我们展示如何操作。

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

就是这样,其他一切都包含在.cpp中。

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


复制自我为物理专业研究生写的维基文章。

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

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

让我宝贝

或者你永远不会忘记你的第一次

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

什么是Make?我为什么要关心?

名为make的工具是构建依赖项管理器。也就是说,它需要知道需要执行哪些命令才能从源文件,目标文件,库,标题等集合中获取软件项目的顺序.-其中一些最近可能已更改---并将它们变成一个正确的最新版本的程序。

实际上你也可以将make用于其他事情,但我不会谈论这个。

一个简单的Makefile

假设您有一个包含以下内容的目录:tool tool.cc tool.o support.cc support.hhsupport.o,它们依赖于root并且应该被编译成一个名为tool的程序,并假设你一直在攻击源文件(这意味着现有的tool现在已经过时)并且想要编译程序。

你自己可以做到这一点

1)检查support.ccsupport.hh是否比support.o更新,如果是,则运行如下命令

1
g++ -g -c -pthread -I/sw/include/root support.cc

2)检查support.hhtool.cc是否比tool.o更新,如果是,则运行如下命令

1
g++ -g  -c -pthread -I/sw/include/root tool.cc

3)检查tool.o是否比tool更新,如果是,则运行如下命令

1
2
3
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

唷!太麻烦了!有很多值得记住的错误和几次出错的机会。 (顺便说一句 - 这里展示的命令行的细节取决于我们的软件环境。这些在我的计算机上工作。)

当然,您每次都可以运行所有三个命令。这样做可行,但不能很好地扩展到大量的软件(比如DOGS需要超过15分钟从我的MacBook上完全编译)。

相反,您可以像这样写一个名为makefile的文件:

1
2
3
4
5
6
7
8
9
10
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。这将自动执行上面显示的三个步骤。

这里的非缩进行具有"target:dependencies"形式,并告诉make如果任何依赖项比目标更新,则应该运行相关的命令(缩进行)。这就是依赖关系线描述了需要重建的逻辑以适应各种文件的变化。如果support.cc更改意味着必须重建support.o,但tool.o可以保持不变。当support.o更改tool时必须重建。

与每个依赖关系线相关联的命令将通过选项卡(见下文)进行设置,以修改目标(或至少触摸它以更新修改时间)。

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

此时,我们的makefile只是记住需要做的工作,但我们仍然需要弄清楚并完整地输入每个所需的命令。它不一定是这样的:make是一个强大的语言,包含变量,文本操作函数和一大堆内置规则,这些规则可以使我们更容易。

制作变量

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

利用这一点可以让我们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

这更容易键入,更易读。

请注意

  • 我们仍然明确说明每个目标文件和最终可执行文件的依赖关系
  • 我们必须明确键入两个源文件的编译规则
  • 隐式和模式规则

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

  • 后缀规则(在GNU make中被认为是过时的,但为了向后兼容而保留)
  • 隐含规则
  • 模式规则
  • 内置隐式规则,下面将讨论一些规则。模式规则以类似的形式指定

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

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

    内置规则

    Make有一大堆内置规则,这意味着很多时候,一个项目可以通过一个非常简单的makefile进行编译。

    GNU make c源文件的内置规则是上面展示的。类似地,我们使用类似$(CXX) -c $(CPPFLAGS) $(CFLAGS)的规则从c ++源文件创建目标文件

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

    内置规则使用的变量

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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,它将使用文件中找到的第一个目标(在本例中为all),但您也可以命名目标以获取在这种情况下make clean删除目标文件的目标。

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

    一些神秘的改进

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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

    请注意

  • 源文件不再有任何依赖行了!?!
  • 有一些奇怪的魔法与.depend和依赖有关
  • 如果你执行make然后ls -A,你会看到一个名为.depend的文件,其中包含看起来像make dependency lines的内容
  • 其他阅读

  • GNU制作手册
  • Recursive Make Considered有害写一个不太理想的makefile的常用方法,以及如何避免它。
  • 知道错误和历史记录

    make的输入语言是空格敏感的。特别是依赖关系后的操作行必须以制表符开头。但是一系列空格看起来可能相同(实际上有些编辑器会将选项卡静默转换为空格,反之亦然),这会导致make文件看起来正确并且仍然无效。这在早期被确定为一个错误但是(故事发生)没有修复,因为已经有10个用户。

    好。


    我一直认为通过一个详细的例子更容易学习,所以这就是我对makefile的看法。对于每个部分,您有一行不缩进的行,它显示该部分的名称,后跟依赖项。依赖项可以是其他部分(将在当前部分之前运行)或文件(如果更新将导致下次运行make时再次运行当前部分)。

    这是一个快速示例(请记住,我使用4个空格,我应该使用选项卡,Stack Overflow不会让我使用选项卡):

    1
    2
    3
    4
    5
    a3driver: a3driver.o
        g++ -o a3driver a3driver.o

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

    键入make时,将选择第一个部分(a3driver)。 a3driver依赖于a3driver.o,所以它将转到该部分。 a3driver.o依赖于a3driver.cpp,所以它只会在a3driver.cpp自上次运行以来发生变化时运行。假设它已经(或从未运行过),它会将a3driver.cpp编译为.o文件,然后返回a3driver并编译最终的可执行文件。

    由于只有一个文件,它甚至可以简化为:

    1
    2
    a3driver: a3driver.cpp
        g++ -o a3driver a3driver.cpp

    我展示第一个例子的原因是它显示了makefile的强大功能。如果需要编译另一个文件,可以添加另一个部分。这是一个带有secondFile.cpp的示例(在名为secondFile.h的头文件中加载):

    1
    2
    3
    4
    5
    6
    7
    8
    a3driver: a3driver.o secondFile.o
        g++ -o a3driver a3driver.o secondFile.o

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

    secondFile.o: secondFile.cpp secondFile.h
        g++ -c secondFile.cpp

    这样,如果你在secondFile.cpp或secondFile.h中更改某些东西并重新编译,它只会重新编译secondFile.cpp(而不是a3driver.cpp)。或者,如果你在a3driver.cpp中更改某些内容,它将不会重新编译secondFile.cpp。

    如果您对此有任何疑问,请与我们联系。

    包括名为"all"的部分和名为"clean"的部分也是传统的。"all"通常会构建所有可执行文件,而"clean"将删除像".o"文件和可执行文件这样的"构建工件":

    1
    2
    3
    4
    5
    all: a3driver ;

    clean:
        # -f so this will succeed even if the files don't exist
        rm -f a3driver a3driver.o

    编辑:我没注意到你在Windows上。我认为唯一的区别是将-o a3driver更改为-o a3driver.exe


    为什么每个人都喜欢列出源文件?一个简单的find命令可以轻松地处理这个问题。

    这是一个简单的C ++ Makefile的例子。只需将其放在包含.C文件的目录中,然后键入make ...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    appname := myapp

    CXX := clang++
    CXXFLAGS := -std=c++11

    srcfiles := $(shell find . -name"*.C")
    objects  := $(patsubst %.C, %.o, $(srcfiles))

    all: $(appname)

    $(appname): $(objects)
        $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

    depend: .depend

    .depend: $(srcfiles)
        rm -f ./.depend
        $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

    clean:
        rm -f $(objects)

    dist-clean: clean
        rm -f *~ .depend

    include .depend


    老问题,我知道,但对于后代。你有两个选择。

    选项1:最简单的makefile = NO MAKEFILE。

    将"a3driver.cpp"重命名为"a3a.cpp",然后在命令行上写入:

    1
    nmake a3a.exe

    就是这样。如果你正在使用gnu-make使用"make"或"gmake"或其他什么。

    选项2:2行makefile。

    1
    2
    a3a.exe: a3driver.obj
            link /out:a3a.exe a3driver.obj

    瞧。


    您的make文件将具有一个或两个依赖关系规则,具体取决于您是使用单个命令编译和链接,还是使用一个用于编译的命令和一个用于链接的命令。

    依赖关系是一个规则树,如下所示:

    1
    2
    3
    4
    5
    main_target : source1 source2 etc
       command to build main_target from sources

    source1 : dependents for source1
       command to build source1

    目标命令后必须有一个空行,命令前一定不能有空行。 makefile中的第一个目标是总体目标,仅当第一个目标依赖于它们时才构建其他目标。

    所以你的makefile看起来像这样。

    1
    2
    3
    4
    5
    a3a.exe : a3driver.obj
       link /out:a3a.exe a3driver.obj

    a3driver.obj : a3driver.cpp
       cc a3driver.cpp

    我建议:

    1
    2
    tool: tool.o file1.o file2.o
            $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@

    要么

    1
    2
    LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
    tool: tool.o file1.o file2.o

    后一个建议稍好一些,因为它重用了GNU make隐式规则。但是,为了工作,源文件必须具有与最终可执行文件相同的名称(即:tool.ctool)。

    请注意,没有必要声明来源。使用隐式规则生成中间目标文件。因此,这个Makefile适用于C和C ++(以及Fortran等...)。

    另请注意,默认情况下,Makefile使用$(CC)作为链接器。 $(CC)无法链接C ++对象。我们仅因此而修改LINK.o。如果要编译C代码,则不必强制LINK.o值。

    当然,您还可以使用变量CFLAGS添加编译标志,并在LDLIBS中添加库。例如:

    1
    2
    CFLAGS = -Wall
    LDLIBS = -lm

    一方面注意:如果你必须使用外部库,我建议使用pkg-config来正确设置CFLAGSLDLIBS

    1
    2
    CFLAGS += $(shell pkg-config --cflags libssl)
    LDLIBS += $(shell pkg-config --libs libssl)

    细心的读者注意,如果更改了一个标头,则Makefile无法正确重建。添加这些行来解决问题:

    1
    2
    override CPPFLAGS += -MMD
    include $(wildcard *.d)

    -MMD允许构建包含有关头依赖关系的Makefile片段的.d文件。第二行只是使用它们。

    当然,编写良好的Makefile还应包含cleandistclean规则:

    1
    2
    3
    4
    5
    clean:
            $(RM) *.o *.d

    distclean: clean
            $(RM) tool

    注意,$(RM)相当于rm -f,但最好不要直接调用rm

    all规则也表示赞赏。为了工作,它应该是您文件的第一条规则:

    1
    all: tool

    您还可以添加install规则:

    1
    2
    3
    PREFIX = /usr/local
    install:
            install -m 755 tool $(DESTDIR)$(PREFIX)/bin

    DESTDIR默认为空。用户可以将其设置为将程序安装到备用系统(交叉编译过程必需)。用于多次分发的软件包维护者也可以更改PREFIX,以便在/usr中安装软件包。

    最后一句话,不要将源文件放在子目录中。如果您真的想这样做,请将此Makefile保留在根目录中,并使用完整路径来识别您的文件(即subdir/file.o)。

    总而言之,您的完整Makefile应如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
    PREFIX = /usr/local
    override CPPFLAGS += -MMD
    include $(wildcard *.d)

    all: tool
    tool: tool.o file1.o file2.o
    clean:
            $(RM) *.o *.d
    distclean: clean
            $(RM) tool
    install:
            install -m 755 tool $(DESTDIR)$(PREFIX)/bin


    我用过friedmud的答案。我对此进行了一段时间的研究,这似乎是一个很好的入门方式。此解决方案还有一个定义良好的添加编译器标志的方法。我再次回答,因为我做了更改,使其在我的环境,Ubuntu和g ++中工作。有时,更多工作实例是最好的老师。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    appname := myapp

    CXX := g++
    CXXFLAGS := -Wall -g

    srcfiles := $(shell find . -maxdepth 1 -name"*.cpp")
    objects  := $(patsubst %.cpp, %.o, $(srcfiles))

    all: $(appname)

    $(appname): $(objects)
        $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

    depend: .depend

    .depend: $(srcfiles)
        rm -f ./.depend
        $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

    clean:
        rm -f $(objects)

    dist-clean: clean
       rm -f *~ .depend

    include .depend

    makefile似乎非常复杂。我正在使用一个,但它产生了一个与不链接g ++库有关的错误。这种配置解决了这个问题。