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

我们被要求使用一个Makefile来为我们的项目把所有东西都拉到一起,但是我们的教授从未向我们展示过如何去做。

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

就是这样,其他所有东西都包含在.cpp

我将如何去做一个简单的Makefile来创build一个名为a3a.exe的可执行文件?

复制从我为物理学gradle生写的wiki文章。

由于这是unix的可执行文件没有扩展名。

有一件事要注意的是, root-config是一个提供正确的编译和链接标志的工具; 以及针对root用户构build应用程序的正确库。 这只是与本文件的原始受众相关的细节。

让我宝贝

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

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

什么是制造? 为什么要关心?

这个名为make的工具是一个构build依赖pipe理器。 也就是说,它需要知道哪些命令需要按照什么样的顺序执行,以便从源文件,目标文件,库,头文件等等中获取软件项目 – 其中一些最近可能已经改变—把它们变成一个正确的最新版本的程序。

其实你也可以用make来做其他的事情,但我不会谈论这个。

一个简单的Makefile

假设你有一个目录,其中包含: tool tool.cc tool.o support.cc support.hhsupport.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 。 这将自动执行上述三个步骤。

这里的非缩进行的forms为“target:dependencies”,并告诉make如果任何依赖关系比目标更新,则应该运行关联的命令(缩进行)。 这就是依赖行描述了需要重build的逻辑以适应各种文件的变化。 如果support.cc发生变化,则意味着support.o必须重build,但是tool.o可以单独保留。 当support.o改变tool必须重build。

与每个依赖线关联的命令是通过一个选项卡(见下面)来修改目标(或至less触摸它来更新修改时间)。

variables,内置规则和其他好东西

在这一点上,我们的makefile仅仅是记住了需要做的工作,但是我们仍然需要弄清楚和input每一个需要的命令。 它不一定是这样的:make是一个强大的语言,包含variables,文本处理函数和一整套内置的规则,可以使我们更容易。

variables

访问makevariables的语法是$(VAR)

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

你可以在规则中使用variables,比如我们的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 

这是一个更具可读性,但仍然需要大量的打字

做function

GNU make支持从文件系统或系统上的其他命令访问信息的各种function。 在这种情况下,我们感兴趣的是$(shell ...) ,它扩展到参数的输出, $(subst opat,npat,text)npatreplace$(subst opat,npat,text)所有opat实例。

利用这个优势给我们:

 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 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 

这是更容易打字,更可读。

注意到

  1. 我们仍然明确指出每个对象文件和最终可执行文件的依赖关系
  2. 我们必须明确地input两个源文件的编译规则

隐式和模式规则

我们通常会期望所有的c ++源文件都应该以同样的方式处理,并且提供三种方式来陈述它

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

隐含的规则是内置的,一些将在下面讨论。 模式规则以类似的forms指定

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

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

内置规则

Make有一整套内置的规则,这就意味着一个项目实际上可以通过一个非常简单的makefile进行编译。

GNU make c源文件的内置规则就是上面展示的规则。 同样,我们使用像$(CXX) -c $(CPPFLAGS) $(CFLAGS)这样的规则从c ++源文件创build目标文件。

单个对象文件使用$(LD) $(LDFLAGS) no $(LOADLIBES) $(LDLIBS) ,但是在我们的例子中这不起作用,因为我们要链接多个对象文件。

内置规则使用的variables

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

  • CC – 要使用的C编译器
  • CXX – 要使用的c ++编译器
  • LD – 要使用的链接器
  • CFLAGS – c源文件的编译标志
  • CXXFLAGS – c ++源文件的编译标志
  • CPPFLAGS – c预处理器的标志(通常包括在命令行中定义的文件path和符号),由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,它会使用文件中find的第一个目标(在本例中为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 

注意到

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

其他阅读

  • GNU make手册
  • recursion制作考虑在编写比最佳的makefile文件的常见方式上是有害的 ,以及如何避免它。

知道错误和历史笔记

make的input语言是空白敏感的。 特别是依赖关系之后的行为行必须以制表符开头 。 但是一系列的空间可能看起来是一样的(事实上,有编辑会悄悄地将制表符转换为空格,反之亦然),这会导致make文件看起来正确,但仍然无法工作。 这被确定为一个早期的错误,但( 故事 )不是固定的,因为已经有10个用户。

我一直认为这是一个详细的例子,这是更容易学习,所以这里是我怎么想makefile。 对于每一部分,您都有一行没有缩进的行,它显示了依赖关系之后的部分的名称。 依赖关系可以是其他部分(将在当前部分之前运行)或文件(如果更新将会导致当前部分在下一次运行make时再次运行)。

这里有一个简单的例子(请记住,我使用4个空格,我应该使用一个标签,堆栈溢出不会让我使用标签):

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

当你inputmake ,它会select第一部分(a3driver)。 a3driver取决于a3driver.o,所以它会去那个部分。 a3driver.o依赖于a3driver.cpp,因此只有在a3driver.cpp自从上次运行后发生更改时才会运行。 假设它已经(或从未运行过),它将编译a3driver.cpp到.o文件,然后返回到a3driver并编译最终的可执行文件。

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

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

我展示第一个例子的原因是它显示了makefile的function。 如果您需要编译另一个文件,您可以添加另一个部分。 下面是一个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”通常会build立所有的可执行文件,而“clean”将删除“.o文件”和可执行文件“build artifacts”:

 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

为什么每个人都喜欢列出源文件? 一个简单的查找命令可以很容易地处理。

下面是一个简单的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 

老问题,我知道,但为了后代。 你有两个select。

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

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

 nmake a3a.exe 

就是这样。 如果你用gnu-make使用make或者gmake等等。

选项2:一个2行makefile。

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

瞧。

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

依赖是一个看起来像这样的规则树:

 main_target : source1 source2 etc command to build main_target from sources source1 : dependents for source1 command to build source1 

在目标命令之后必须有一个空行,并且在命令之前不能有空行。 makefile中的第一个目标是总体目标,其他目标只有在第一个目标依赖于它们时才被创build。

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

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

我用了friedmud的答案 我看了一会儿,这似乎是一个很好的开始。 该解决scheme还有一个定义好的添加编译器标志的方法。 我再次回答,因为我做了改变,使其在我的环境,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 

makefiles看起来非常复杂。 我正在使用一个,但它产生了一个错误,在g ++库中没有链接。 这个configuration解决了这个问题。

这是一个简单的Makefile

 tool: tool.o file1.o file2.o override CPPFLAGS += -MMD LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH) include $(wildcard *.d) 

为了有一个全function的Makefile ,我build议也加上:

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

一些注意事项:

  • 我们使用隐式规则来创build最终的可执行文件。 所以,为了工作,源文件必须与最终可执行文件名相同(即: tool.ctool )。
  • 默认情况下,Makefile使用$(CC)作为链接器,并且不能链接C ++对象。 我们修改LINK.o以强制Makefile使用$(CXX)链接对象。
  • 通过添加-MMD标志并包括生成的.d文件自动处理标题的依赖关系
  • 没有必要声明来源。 中间对象文件是使用隐式规则生成的。 因此,这个Makefile适用于C和C ++(也适用于Fortran等)。
  • 如果你想处理子目录中的文件,你必须把这个Makefile保存在根目录下,并在源文件名中提供path(即subdir/file.o
  • 您可能想要添加install:all: rules
  • 像往常一样,您可以在$(CFLAGS)$(LDLIBS)等等中添加编译和链接标志
  • $(RM)默认值是rm -f