我们需要使用Makefile将所有内容整合到我们的项目中,但是我们的教授从未向我们展示如何使用。
我只有一个文件a3driver.cpp
。驱动程序从位置导入一个类"/user/cse232/Examples/example32.sequence.cpp"
。
而已。其他所有内容都包含在中.cpp
。
我将如何制作一个简单的Makefile来创建一个名为的可执行文件a3a.exe
?
我们需要使用Makefile将所有内容整合到我们的项目中,但是我们的教授从未向我们展示如何使用。
我只有一个文件a3driver.cpp
。驱动程序从位置导入一个类"/user/cse232/Examples/example32.sequence.cpp"
。
而已。其他所有内容都包含在中.cpp
。
我将如何制作一个简单的Makefile来创建一个名为的可执行文件a3a.exe
?
Answers:
由于这是针对Unix的,因此可执行文件没有任何扩展名。
需要注意的一件事root-config
是一个提供正确编译和链接标志的实用程序。以及用于针对root构建应用程序的正确库。那只是与本文档原始读者有关的一个细节。
还是您永远不会忘记第一次
关于make的介绍性讨论,以及如何编写简单的makefile
什么是品牌?我为什么要在乎呢?
名为Make的工具是一个构建依赖项管理器。也就是说,它需要知道从源文件,目标文件,库,头文件等的集合中以什么顺序执行您的软件项目需要执行哪些命令-其中一些可能已更改最近-并将其转换为该程序的正确最新版本。
实际上,您也可以将Make用于其他用途,但我不会在此进行讨论。
普通的Makefile
假设您有一个包含以下内容的目录:tool
tool.cc
tool.o
support.cc
support.hh
,并且 support.o
依赖于root
并且应该被编译成一个名为的程序tool
,并且假设您一直在对源文件进行黑客入侵(这意味着现有文件tool
已经过时)并且想要编译程序。
要自己做到这一点,你可以
检查是否有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
!真麻烦!有很多要记住的地方,也有很多犯错误的机会。(顺便说一句,这里显示的命令行的详细信息取决于我们的软件环境。这些可以在我的计算机上工作。)
当然,您每次可以只运行所有三个命令。那行得通,但是不能很好地扩展到大量软件上(例如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
在命令行中输入 它将自动执行上面显示的三个步骤。
这里的无缩进行格式为“目标:依赖关系”,并告诉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)
替换文本中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内置规则。同样,我们使用类似规则从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 ++使用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时,它将使用文件中找到的第一个目标(在本例中为全部),但是您也可以命名要获取的目标,在这种情况下,make会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的输入语言对空格敏感。特别是,依赖项之后的操作行必须以tab开头。但是一系列空格看起来可能是相同的(实际上,有一些编辑器会静默地将制表符转换为空格,反之亦然),这将导致Make文件看起来正确但仍然无法使用。早期已将其识别为错误,但(故事继续)该错误尚未修复,因为已经有10个用户。
(摘录自我为物理研究生写的一篇Wiki帖子。)
-pthread
标志原因gcc
定义必要的宏,-D_REENTRANT
是不必要的。
root-config
)。如果有的话,应该提出一种具有相同功能的更通用的替代方案,否则就应该忽略掉。我没有拒绝投票,因为列出了最常用的make宏。
我一直认为通过一个详细的示例可以更轻松地学习它,因此这就是我对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
。
为什么每个人都喜欢列出源文件?一个简单的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
您有两个选择。
选项1:最简单的makefile = NO MAKEFILE。
将“ a3driver.cpp”重命名为“ a3a.cpp”,然后在命令行上输入:
nmake a3a.exe
就是这样。如果您使用的是GNU Make,请使用“ make”或“ gmake”或其他名称。
选项2:两行生成文件。
a3a.exe: a3driver.obj
link /out:a3a.exe a3driver.obj
nmake
。该link
命令行也看起来非常具体到特定的编译器,并应在其中一个最起码的文件。
根据您是使用单个命令进行编译和链接,还是使用一个用于编译的命令和一个用于链接的命令,您的Make文件将具有一个或两个依赖项规则。
依赖关系是一棵规则树,如下所示(请注意,缩进必须是TAB):
main_target : source1 source2 etc
command to build main_target from sources
source1 : dependents for source1
command to build source1
有必须是一个目标的命令后一个空行,而且必须不能是命令之前,一个空行。makefile中的第一个目标是总体目标,只有第一个目标依赖于其他目标时,才会构建其他目标。
因此,您的makefile将如下所示。
a3a.exe : a3driver.obj
link /out:a3a.exe a3driver.obj
a3driver.obj : a3driver.cpp
cc a3driver.cpp
我建议(请注意,缩进是TAB):
tool: tool.o file1.o file2.o
$(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
要么
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
tool: tool.o file1.o file2.o
后一种建议稍好一些,因为它重用了GNU Make隐式规则。但是,为了起作用,源文件必须与最终的可执行文件具有相同的名称(即:tool.c
和tool
)。
注意,没有必要声明源。使用隐式规则生成中间目标文件。因此,这Makefile
适用于C和C ++(以及Fortran等)。
还要注意,默认情况下,Makefile $(CC)
用作链接器。$(CC)
不适用于链接C ++对象文件。我们LINK.o
仅因此修改。如果要编译C代码,则不必强制使用该LINK.o
值。
当然,您还可以在变量中CFLAGS
添加编译标志,并在中添加库LDLIBS
。例如:
CFLAGS = -Wall
LDLIBS = -lm
附带说明:如果必须使用外部库,建议您使用pkg-config来正确设置CFLAGS
和LDLIBS
:
CFLAGS += $(shell pkg-config --cflags libssl)
LDLIBS += $(shell pkg-config --libs libssl)
细心的读者会注意到,Makefile
如果更改了一个标头,则无法正确重建。添加以下行以解决问题:
override CPPFLAGS += -MMD
include $(wildcard *.d)
-MMD
允许构建.d文件,其中包含有关标头依赖项的Makefile片段。第二行仅使用它们。
可以肯定,一个写得很好的Makefile还应该包括clean
与distclean
规则:
clean:
$(RM) *.o *.d
distclean: clean
$(RM) tool
请注意,$(RM)
等效于rm -f
,但是不rm
直接调用是一个好习惯。
该all
规则也受到赞赏。为了工作,这应该是文件的第一条规则:
all: tool
您还可以添加一条install
规则:
PREFIX = /usr/local
install:
install -m 755 tool $(DESTDIR)$(PREFIX)/bin
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
make
我知道的任何实现(GNU Make和BSD Make)都不需要规则之间的空行。但是,它存在大量的make
实现,它们都有自己的bug ^ Wspecificity。
我用了弗里德穆德的回答。我研究了一段时间,这似乎是入门的好方法。此解决方案也具有添加编译器标志的明确定义的方法。我再次回答,因为我进行了更改以使其可以在我的环境(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
Makefile似乎非常复杂。我正在使用一个,但是它生成与未链接g ++库有关的错误。此配置解决了该问题。