How to make a SIMPLE C++ Makefile?
我们需要使用Makefile将所有内容整合到我们的项目中,但我们的教授从未向我们展示如何操作。
我只有一个文件,
驱动程序从位置
就是这样,其他一切都包含在
我如何制作一个简单的Makefile来创建一个名为
复制自我为物理专业研究生写的维基文章。
由于这是针对unix的,因此可执行文件没有扩展名。
需要注意的一点是,
让我宝贝
或者你永远不会忘记你的第一次
关于make的介绍性讨论,以及如何编写简单的makefile
什么是Make?我为什么要关心?
名为make的工具是构建依赖项管理器。也就是说,它需要知道需要执行哪些命令才能从源文件,目标文件,库,标题等集合中获取软件项目的顺序.-其中一些最近可能已更改---并将它们变成一个正确的最新版本的程序。
实际上你也可以将make用于其他事情,但我不会谈论这个。
一个简单的Makefile
假设您有一个包含以下内容的目录:
你自己可以做到这一点
1)检查
1 | g++ -g -c -pthread -I/sw/include/root support.cc |
2)检查
1 | g++ -g -c -pthread -I/sw/include/root tool.cc |
3)检查
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上完全编译)。
相反,您可以像这样写一个名为
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 |
然后在命令行输入
这里的非缩进行具有"target:dependencies"形式,并告诉make如果任何依赖项比目标更新,则应该运行相关的命令(缩进行)。这就是依赖关系线描述了需要重建的逻辑以适应各种文件的变化。如果
与每个依赖关系线相关联的命令将通过选项卡(见下文)进行设置,以修改目标(或至少触摸它以更新修改时间)。
变量,内置规则和其他好东西
此时,我们的makefile只是记住需要做的工作,但我们仍然需要弄清楚并完整地输入每个所需的命令。它不一定是这样的:make是一个强大的语言,包含变量,文本操作函数和一大堆内置规则,这些规则可以使我们更容易。
制作变量
访问make变量的语法是
分配给make变量的语法是:
(或
您可以在规则中使用变量,例如我们的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支持各种函数,用于从文件系统或系统上的其他命令访问信息。在这种情况下,我们感兴趣的是扩展到参数输出的
利用这一点可以让我们:
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提供了三种方式来说明这一点
内置隐式规则,下面将讨论一些规则。模式规则以类似的形式指定
1 2 | %.o: %.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $< |
这意味着通过运行显示的命令从c源文件生成目标文件,其中"自动"变量
内置规则
Make有一大堆内置规则,这意味着很多时候,一个项目可以通过一个非常简单的makefile进行编译。
GNU make c源文件的内置规则是上面展示的。类似地,我们使用类似
单个对象文件使用
内置规则使用的变量
内置规则使用一组标准变量,允许您指定本地环境信息(例如在何处查找ROOT包含文件),而无需重写所有规则。我们最感兴趣的是:
基本的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),但您也可以命名目标以获取在这种情况下
我们仍然对所有依赖项进行了硬编码。
一些神秘的改进
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 |
请注意
其他阅读
知道错误和历史记录
make的输入语言是空格敏感的。特别是依赖关系后的操作行必须以制表符开头。但是一系列空格看起来可能相同(实际上有些编辑器会将选项卡静默转换为空格,反之亦然),这会导致make文件看起来正确并且仍然无效。这在早期被确定为一个错误但是(故事发生)没有修复,因为已经有10个用户。
好。
我一直认为通过一个详细的例子更容易学习,所以这就是我对makefile的看法。对于每个部分,您有一行不缩进的行,它显示该部分的名称,后跟依赖项。依赖项可以是其他部分(将在当前部分之前运行)或文件(如果更新将导致下次运行
这是一个快速示例(请记住,我使用4个空格,我应该使用选项卡,Stack Overflow不会让我使用选项卡):
1 2 3 4 5 | a3driver: a3driver.o g++ -o a3driver a3driver.o a3driver.o: a3driver.cpp g++ -c a3driver.cpp |
键入
由于只有一个文件,它甚至可以简化为:
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上。我认为唯一的区别是将
为什么每个人都喜欢列出源文件?一个简单的find命令可以轻松地处理这个问题。
这是一个简单的C ++ 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 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隐式规则。但是,为了工作,源文件必须具有与最终可执行文件相同的名称(即:
请注意,没有必要声明来源。使用隐式规则生成中间目标文件。因此,这个
另请注意,默认情况下,Makefile使用
当然,您还可以使用变量
1 2 | CFLAGS = -Wall LDLIBS = -lm |
一方面注意:如果你必须使用外部库,我建议使用pkg-config来正确设置
1 2 | CFLAGS += $(shell pkg-config --cflags libssl) LDLIBS += $(shell pkg-config --libs libssl) |
细心的读者注意,如果更改了一个标头,则
1 2 | override CPPFLAGS += -MMD include $(wildcard *.d) |
当然,编写良好的Makefile还应包含
1 2 3 4 5 | clean: $(RM) *.o *.d distclean: clean $(RM) tool |
注意,
1 | all: tool |
您还可以添加
1 2 3 | PREFIX = /usr/local install: install -m 755 tool $(DESTDIR)$(PREFIX)/bin |
最后一句话,不要将源文件放在子目录中。如果您真的想这样做,请将此
总而言之,您的完整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 ++库有关的错误。这种配置解决了这个问题。