如何制作SIMPLE C++ Makefile?

Bef*_*all 285 c++ makefile

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

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

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

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

dmc*_*kee 531

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

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

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

让我宝贝

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

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

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

名为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
Run Code Online (Sandbox Code Playgroud)

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

g++ -g  -c -pthread -I/sw/include/root tool.cc
Run Code Online (Sandbox Code Playgroud)

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

g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
Run Code Online (Sandbox Code Playgroud)

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

当然,您每次都可以运行所有三个命令.这样做可行,但不能很好地扩展到大量的软件(比如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
Run Code Online (Sandbox Code Playgroud)

然后只需make在命令行输入.这将自动执行上面显示的三个步骤.

这里的非缩进行具有"target:dependencies"形式,并告诉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
Run Code Online (Sandbox Code Playgroud)

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

制作功能

GNU make支持各种函数,用于从文件系统或系统上的其他命令访问信息.在这种情况下,我们感兴趣的$(shell ...)是扩展到参数的输出,并$(subst opat,npat,text)替换文本中的所有opatwith的实例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
Run Code Online (Sandbox Code Playgroud)

这更容易键入,更易读.

请注意

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

隐式和模式规则

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

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

内置隐式规则,下面将讨论一些规则.模式规则以类似的形式指定

%.o: %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c $<
Run Code Online (Sandbox Code Playgroud)

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

内置规则

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

GNU make c源文件的内置规则是上面展示的.同样,我们使用类似规则从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-preprocessor的标志(通常包括命令行中定义的文件路径和符号)
  • 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
Run Code Online (Sandbox Code Playgroud)

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

请注意,如果在没有参数的情况下调用make,它将使用文件中找到的第一个目标(在本例中为all),但您也可以命名目标以获取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
Run Code Online (Sandbox Code Playgroud)

请注意

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

其他阅读

知道错误和历史记录

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

  • 它*很长,但也是让读者处理非平凡的makefile的最小值.为什么?因为在普通的makefile中有一个*lot*.即便在这,我也遗漏了`=`和`:=`之间的重要区别.原件不供公众使用,在任何情况下我也不会发布裸链接. (71认同)
  • 我认为你刚刚做了这么长时间以至于你不知道初学者的"简单"意味着什么;)简单!=文字墙 (26认同)
  • @Brendan:第一个对你来说很简单吗?它只是依赖行,每行一个动作:没有变量,没有隐式命令或模式,没有聪明. (15认同)
  • 这种生成依赖关系的方法已经过时且实际上有害.请参阅[高级自动依赖关系生成](http://mad-scientist.net/make/autodep.html). (8认同)
  • @jcoe它执行一个不必要的额外预处理器传递来生成依赖项.做不必要的工作只会消散融化冰柱的热量,并且在更大的范围内,接近我们宇宙的热量死亡. (6认同)
  • `-pthread`标志导致`gcc`定义必要的宏,`-D_REENTRANT`是不必要的. (5认同)
  • @jcoe 除了Maxim 的高昂成本之外,还有一个非常直接的成本就是让您的构建时间更长。一旦项目变得超过几个开发人员并且几个分数文件编译时间就会成为一个问题,如果不明智地使用 `make` 特性,一个人可能会生成极其缓慢且仍然不正确的构建。如果您有时间阅读“递归使被认为有害”。我在这里做错了,因为(1)货物崇拜和(2)当有人问起时,我知道如何解释。 (2认同)
  • 可能“有害”有点过分,但鉴于至少 GCC 3 以来明确的依赖生成阶段或目标已经过时,我真的认为我们都应该超越它们。http://bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/#comment-50775 (2认同)
  • 确实,公认的答案不应该依赖于非常具体的软件(`root-config`)。如果有的话,应该提出一个具有相同功能的更通用的替代方案,或者应该忽略它。我没有因为最常用的 make 宏的列表和解释而投反对票。 (2认同)

Rei*_*ica 54

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

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

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

a3driver.o: a3driver.cpp
    g++ -c a3driver.cpp
Run Code Online (Sandbox Code Playgroud)

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

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

a3driver: a3driver.cpp
    g++ -o a3driver a3driver.cpp
Run Code Online (Sandbox Code Playgroud)

我展示第一个例子的原因是它显示了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
Run Code Online (Sandbox Code Playgroud)

这样,如果你在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
Run Code Online (Sandbox Code Playgroud)

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

  • 据我所知,仅当您有空格时才会出现该错误消息。确保您没有任何以空格开头的行(空格+制表符将给出该错误)。那是我唯一能想到的。 (2认同)

小智 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
Run Code Online (Sandbox Code Playgroud)

  • 是的,但是问题的标题中确实带有* SIMPLE * ;-) (3认同)
  • 不自动查找源文件的原因是,可以具有需要不同文件的不同构建目标。 (2认同)

小智 13

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

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

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

nmake a3a.exe
Run Code Online (Sandbox Code Playgroud)

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

选项2:2行makefile.

a3a.exe: a3driver.obj
    link /out:a3a.exe a3driver.obj
Run Code Online (Sandbox Code Playgroud)

瞧.

  • 如果它不以OP环境的细节为前提,那将是一个很好的答案。是的,它们在Windows上,但这并不意味着它们在使用`nmake`。“ link”命令行看起来也非常特定于特定的编译器,并且至少应记录哪个编译器。 (2认同)

Joh*_*ler 6

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

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

main_target : source1 source2 etc
    command to build main_target from sources

source1 : dependents for source1
    command to build source1
Run Code Online (Sandbox Code Playgroud)

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

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

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

a3driver.obj : a3driver.cpp
    cc a3driver.cpp
Run Code Online (Sandbox Code Playgroud)


Jér*_*ler 6

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

tool: tool.o file1.o file2.o
    $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
Run Code Online (Sandbox Code Playgroud)

要么

LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
tool: tool.o file1.o file2.o
Run Code Online (Sandbox Code Playgroud)

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

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

还要注意,默认情况下,Makefile $(CC)用作链接器。$(CC)不适用于链接C ++对象文件。我们LINK.o仅因此修改。如果要编译C代码,则不必强制使用该LINK.o值。

当然,您也可以添加带有变量的编译标志,CFLAGS并在中添加库LDLIBS。例如:

CFLAGS = -Wall
LDLIBS = -lm
Run Code Online (Sandbox Code Playgroud)

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

CFLAGS += $(shell pkg-config --cflags libssl)
LDLIBS += $(shell pkg-config --libs libssl)
Run Code Online (Sandbox Code Playgroud)

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

override CPPFLAGS += -MMD
include $(wildcard *.d)
Run Code Online (Sandbox Code Playgroud)

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

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

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

distclean: clean
    $(RM) tool
Run Code Online (Sandbox Code Playgroud)

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

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

all: tool
Run Code Online (Sandbox Code Playgroud)

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

PREFIX = /usr/local
install:
    install -m 755 tool $(DESTDIR)$(PREFIX)/bin
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)


Vec*_*tec 5

我用过friedmud的答案.我对此进行了一段时间的研究,这似乎是一个开始的好方法.此解决方案还有一个定义良好的添加编译器标志的方法.我再次回答,因为我做了更改,使其在我的环境,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
Run Code Online (Sandbox Code Playgroud)

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