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


302

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

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

而已。其他所有内容都包含在中.cpp

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


9
.EXE因此绝对是Windows。再三考虑...路径是Unix风格的。可能使用Mingw-32。
内森·奥斯曼

2
叹。我想即使您永远不会使用它们,也必须学习每笔交易的基础知识。只需了解事物是如何工作的即可。不过,您总是会在Eclipse之类的IDE中进行开发的机会很好。您将在这里找到有关简单的单行案例的答案,并且有大量的网络教程,但是如果您需要深入的知识,则无法击败O'reilly的书(大多数软件主题都是相同的)。amazon.com/Managing-Projects-Make-Nutshell-Handbooks/dp/… 从amazon,half.com,betterworldbooks eBay上挑选第二张手抄本
Mawg说,要恢复莫妮卡2010年

2
@Dennis发布的链接现在已失效,但是可以在这个archive.org页面中找到相同的材料。
GuilhermeSalomé18年

我喜欢这个人的想法。(hiltmon.com/blog/2013/07/03/…)可以轻松修改项目结构以适应需要。我也同意开发人员应该将时间花在automake / autoconf之外的其他事情上。这些工具占有一席之地,但可能不适用于内部项目。我正在构建一个脚本,它将产生这样的项目结构。
荒木大辅'18

@GuilhermeSalomé谢谢,我相信这是最好的简单完整的教程。
Hareen Laks

Answers:


559

由于这是针对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
    

!真麻烦!有很多要记住的地方,也有很多犯错误的机会。(顺便说一句,这里显示的命令行的详细信息取决于我们的软件环境。这些可以在我的计算机上工作。)

当然,您每次可以只运行所有三个命令。那行得通,但是不能很好地扩展到大量软件上(例如DOGS,它需要15分钟以上的时间才能在我的MacBook上完全编译)。

相反,您可以编写一个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内置规则。同样,我们使用类似规则从C ++源文件创建目标文件$(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时,它将使用文件中找到的第一个目标(在本例中为全部),但是您也可以命名要获取的目标,在这种情况下,make会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帖子。)


9
这种生成依赖关系的方法已经过时,实际上是有害的。请参阅高级自动依赖项生成
Maxim Egorushkin 2011年

5
-pthread标志原因gcc定义必要的宏,-D_REENTRANT是不必要的。
Maxim Egorushkin 2011年

8
@jcoe进行不必要的额外预处理程序来生成依赖关系。做不必要的工作只会消散融化冰柱的热量,并且在更大的范围内,已经接近了我们宇宙的热死亡。
Maxim Egorushkin 2014年

2
也许“有害”有点过分,但是鉴于至少从GCC 3起,明确的依赖关系生成阶段或目标已经过时了,我真的认为我们所有人都应该超越它们。bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/…–
hmijail哀悼辞职者,

2
实际上,可接受的答案不应该依赖于非常特定的软件(root-config)。如果有的话,应该提出一种具有相同功能的更通用的替代方案,否则就应该忽略掉。我没有拒绝投票,因为列出了最常用的make宏。
green diod

56

我一直认为通过一个详细的示例可以更轻松地学习它,因此这就是我对makefile的看法。对于每一节,您都有一行没有缩进的行,它显示了节的名称,后跟相关性。依赖关系可以是其他部分(将在当前部分之前运行)或文件(如果已更新,则将导致当前部分在您下次运行时再次运行make)。

这是一个简单的示例(请记住,我在应该使用制表符的位置使用了4个空格,Stack Overflow不允许我使用制表符):

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并编译最终的可执行文件。

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

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

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

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文件”和可执行文件之类的“生成工件”:

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


我要使用的绝对代码是:p4a.exe:p4driver.cpp g ++ -o p4a p4driver.cpp但是,它告诉我“缺少分隔符”。我正在使用TAB,但仍然可以告诉我。任何的想法?
坠落

2
据我所知,仅当您有空格时才会出现该错误消息。确保您没有任何以空格开头的行(space + tab会出现该错误)。这是我唯一能想到的
。–布兰丹·朗

将来的编辑者注意:即使将标签编辑为答案,StackOverflow也无法呈现标签,因此请不要尝试“修正”我的注释。
布伦丹·朗

35

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

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

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

2
不自动查找源文件的原因是,可以具有需要不同文件的不同构建目标。
hmijail哀悼辞职者,2015年

商定的@hmijail以及包含大量您不希望编译/链接的源/头的子模块...毫无疑问,在许多其他情况下,穷举搜索/使用也不适合。
工程师

为什么要使用“ shell查找”而不是“通配符”?
诺兰

1
@Nolan在源目录树中查找源文件
AlejandroVD

13

您有两个选择。

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

将“ a3driver.cpp”重命名为“ a3a.cpp”,然后在命令行上输入:

nmake a3a.exe

就是这样。如果您使用的是GNU Make,请使用“ make”或“ gmake”或其他名称。

选项2:两行生成文件。

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

3
如果不以OP环境的细节为前提,这将是一个很好的答案。是的,它们在Windows上,但这并不意味着它们正在使用nmake。该link命令行也看起来非常具体到特定的编译器,并应在其中一个最起码的文件。
三点

6

根据您是使用单个命令进行编译和链接,还是使用一个用于编译的命令和一个用于链接的命令,您的Make文件将具有一个或两个依赖项规则。

依赖关系是一棵规则树,如下所示(请注意,缩进必须是TAB):

main_target : source1 source2 etc
    command to build main_target from sources

source1 : dependents for source1
    command to build source1

必须是一个目标的命令后一个空行,而且必须不能是命令之前,一个空行。makefile中的第一个目标是总体目标,只有第一个目标依赖于其他目标时,才会构建其他目标。

因此,您的makefile将如下所示。

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

a3driver.obj : a3driver.cpp
    cc a3driver.cpp

6

我建议(请注意,缩进是TAB):

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

要么

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。例如:

CFLAGS = -Wall
LDLIBS = -lm

附带说明:如果必须使用外部库,建议您使用pkg-config来正确设置CFLAGSLDLIBS

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

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

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

-MMD允许构建.d文件,其中包含有关标头依赖项的Makefile片段。第二行仅使用它们。

可以肯定,一个写得很好的Makefile还应该包括cleandistclean规则:

clean:
    $(RM) *.o *.d

distclean: clean
    $(RM) tool

请注意,$(RM)等效于rm -f,但是不rm直接调用是一个好习惯。

all规则也受到赞赏。为了工作,这应该是文件的第一条规则:

all: tool

您还可以添加一条install规则:

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

DESTDIR默认为空。用户可以将其设置为在其他系统上安装您的程序(对于交叉编译过程是必需的)。多个发行版的软件包维护者也可能会更改PREFIX,以便在中安装软件包/usr

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

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

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

快要结束了:规则之间不应该有空行吗?约翰·诺勒的回答声称。
彼得·莫滕森

make我知道的任何实现(GNU Make和BSD Make)都不需要规则之间的空行。但是,它存在大量的make实现,它们都有自己的bug ^ Wspecificity。
杰罗姆Pouiller

5

我用了弗里德穆德的回答。我研究了一段时间,这似乎是入门的好方法。此解决方案也具有添加编译器标志的明确定义的方法。我再次回答,因为我进行了更改以使其可以在我的环境(Ubuntu和g ++)中工作。有时,最好的老师是更多可行的例子。

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 ++库有关的错误。此配置解决了该问题。

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.