我们需要使用 Makefile 来为我们的项目整合所有内容,但我们的教授从未向我们展示过如何去做。
我只有 一个 文件,a3driver.cpp. 驱动程序从一个位置导入一个类,"/user/cse232/Examples/example32.sequence.cpp".
a3driver.cpp
"/user/cse232/Examples/example32.sequence.cpp"
就是这样。其他所有内容都包含在.cpp.
.cpp
我将如何制作一个简单的 Makefile 来创建一个名为的可执行文件a3a.exe?
a3a.exe
由于这是针对 Unix 的,因此可执行文件没有任何扩展名。
需要注意的一点是,它root-config是一个提供正确编译和链接标志的实用程序;以及用于针对 root 构建应用程序的正确库。这只是与本文档的原始受众相关的一个细节。
root-config
或者你永远不会忘记你第一次被制造
一个关于make的介绍性讨论,以及如何编写一个简单的makefile
什么是制作? 我为什么要关心?
名为Make的工具是一个构建依赖管理器。也就是说,它负责了解需要以什么顺序执行哪些命令,以便从源文件、目标文件、库、头文件等的集合中获取您的软件项目——其中一些可能已经改变最近——并将它们变成正确的最新版本的程序。
实际上,您也可以将 Make 用于其他事情,但我不打算谈论这个。
一个简单的 Makefile
假设您有一个目录,其中包含: ,tool tool.cc tool.o support.cc support.hh它 support.o依赖于root并且应该被编译成一个tool名为tool编译程序。
tool
tool.cc
tool.o
support.cc
support.hh
support.o
root
要自己做,你可以
检查是否support.cc或support.hh比 新support.o,如果是,请运行类似的命令
g++ -g -c -pthread -I/sw/include/root support.cc
检查其中一个support.hh或tool.cc是否比 更新tool.o,如果是,则运行类似的命令
g++ -g -c -pthread -I/sw/include/root tool.cc
检查是否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
呸!多么麻烦!有很多事情要记住,也有很多犯错的机会。(顺便说一句——这里展示的命令行的细节取决于我们的软件环境。这些在我的电脑上工作。)
当然,您可以每次都运行所有三个命令。这会起作用,但它不能很好地扩展到大量软件(比如在我的 MacBook 上从头开始编译需要 15 分钟以上的 DOGS)。
相反,您可以编写一个文件,makefile如下所示:
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
此处未缩进的行具有 “目标:依赖项” 的形式,并告诉 Make,如果任何依赖项比目标更新,则应运行关联的命令(缩进行)。也就是说,依赖行描述了需要重建以适应各种文件中的更改的逻辑。如果support.cc发生更改,则意味着support.o必须重建,但tool.o可以不理会。何时必须重建support.o更改。tool
与每个依赖行关联的命令都用一个选项卡(见下文)设置,应该修改目标(或至少触摸它以更新修改时间)。
在这一点上,我们的 makefile 只是记住了需要做的工作,但我们仍然必须弄清楚并完整地键入每个需要的命令。不一定非要如此:Make 是一种强大的语言,它具有变量、文本操作函数和大量内置规则,可以让我们更轻松地完成此操作。
制作变量
访问 make 变量的语法是$(VAR).
$(VAR)
分配给 Make 变量的语法是:(VAR = A text value of some kind 或VAR := A different text value but ignore this for the moment)。
VAR = A text value of some kind
VAR := 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
$(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
这更容易输入并且更具可读性。
请注意
隐式规则和模式规则
我们通常希望所有 C++ 源文件都应该以相同的方式处理,Make 提供了三种方式来说明这一点:
隐式规则是内置的,下面将讨论一些规则。模式规则以如下形式指定
%.o: %.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $<
这意味着目标文件是通过运行显示的命令从 C 源文件生成的,其中“自动”变量$<扩展为第一个依赖项的名称。
$<
内置规则
Make 有很多内置规则,这意味着很多时候,一个项目可以通过一个非常简单的 makefile 来编译,确实如此。
C 源文件的 GNU make 内置规则就是上面展示的规则。类似地,我们使用类似$(CXX) -c $(CPPFLAGS) $(CFLAGS).
$(CXX) -c $(CPPFLAGS) $(CFLAGS)
单个目标文件使用 链接$(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS),但这在我们的例子中不起作用,因为我们想要链接多个目标文件。
$(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)
内置规则使用的变量
内置规则使用一组标准变量,允许您指定本地环境信息(例如在哪里可以找到 ROOT 包含文件),而无需重写所有规则。我们最感兴趣的是:
CC
CXX
LD
CFLAGS
CXXFLAGS
CPPFLAGS
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 时,它使用在文件中找到的第一个目标(在本例中为全部),但您也可以将目标命名为 get,这就是make clean在这种情况下删除目标文件的原因。
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
ls -A
.depend
其他阅读
了解错误和历史记录
Make 的输入语言对空格敏感。特别是, 依赖项之后的操作行必须以 tab 开头 。但是一系列空格看起来是一样的(确实有些编辑器会默默地将制表符转换为空格,反之亦然),这会导致 Make 文件看起来正确但仍然无法工作。这在早期被确定为一个错误,但是(故事是这样的)它没有被修复,因为已经有 10 个用户。
(这是从我为物理研究生写的 wiki 帖子中复制的。)