如何制作一个SIMPLE C ++ Makefile?
我们被要求使用一个Makefile来为我们的项目整合所有东西,但是我们的教授从来没有告诉我们如何去做。
我只有一个文件, a3driver.cpp
。 驱动程序从位置"/user/cse232/Examples/example32.sequence.cpp"
导入一个类。
就是这样,其他所有内容都包含在.cpp
。
我将如何去做一个简单的Makefile来创建一个名为a3a.exe
的可执行文件?
从我为物理学毕业生写的维基文章中复制。
由于这是用于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.cc
或support.hh
是否比support.o
更新,如果是,则运行类似命令
g++ -g -c -pthread -I/sw/include/root support.cc
2)检查support.hh
或tool.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
唷! 真是麻烦! 有很多事情要记住,有几次犯错误的机会。 (BTW--这里展示的命令行的细节取决于我们的软件环境,这些都可以在我的电脑上运行。)
当然,你可以每次运行所有三个命令。 这是可行的,但不能很好地适应大量的软件(例如DOGS,它可以在MacBook上从头开始编译超过15分钟)。
相反,您可以编写一个名为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
。 这将自动执行上述三个步骤。
这里的非缩进行的形式为“target:dependencies”,并告诉make如果任何依赖项比目标更新,则应该运行关联的命令(缩进行)。 这就是依赖行描述了需要重建以适应各种文件变化的逻辑。 如果support.cc
发生更改,则意味着必须重建support.o
,但tool.o
可以单独保留。 当support.o
更改tool
必须重建。
与每个依赖关系行关联的命令是通过选项卡(见下文)设置的,以修改目标(或至少触摸它来更新修改时间)。
变量,内置规则和其他好东西
在这一点上,我们的makefile只是简单地记住需要做的工作,但我们仍然必须弄清楚并输入完整的每一个需要的命令。 它不一定是这样的:make是一个强大的语言,包含变量,文本处理函数和一整套内置的规则,可以让我们更容易。
变量
访问make变量的语法是$(VAR)
。
分配给make变量的语法是: 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)
用npat
替换$(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
这是更容易打字和更具可读性。
注意到
隐式和模式规则
我们通常会期望所有的c ++源文件都应该以相同的方式处理,并提供三种方式来说明这一点
隐式规则是内置的,其中一些将在下面讨论。 模式规则以类似的形式指定
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
这意味着通过运行显示的命令从c源文件生成目标文件,其中“自动”变量$<
扩展为第一个依赖项的名称。
内置规则
Make有大量的内置规则,这实际上意味着一个项目可以通过一个非常简单的makefile进行编译。
GNU make c源文件的内置规则是上面展示的规则。 同样,我们使用像$(CXX) -c $(CPPFLAGS) $(CFLAGS)
这样的规则从c ++源文件创建目标文件。
单个对象文件使用$(LD) $(LDFLAGS) no $(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,它将使用文件中找到的第一个目标(在本例中为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
注意到
make
那么ls -A
你会看到一个名为.depend
的文件,其中包含看起来像是依赖行的东西 其他阅读
知道错误和历史笔记
make的输入语言对空白敏感。 特别是依赖关系之后的行为行必须以制表符开头。 但是一系列空间可能看起来是一样的(事实上有些编辑器会将制表符默认转换为空格,反之亦然),这会导致make文件看起来正确,但仍然无法正常工作。 这被确定为一个早期的错误,但(故事会)并没有修复,因为已经有10个用户。
我一直认为用一个详细的例子可以更容易地学习,所以这里是我对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
。
为什么每个人都喜欢列出源文件? 一个简单的查找命令可以很容易地处理。
这里有一个简单的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
链接地址: http://www.djcxy.com/p/61437.html