如何制作一个简单的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.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
唷! 多麻烦! 有很多要记住,有几次犯错误的机会。 (顺便说一下,这里展示的命令行的细节取决于我们的软件环境,这些都在我的电脑上运行。)
当然,你可以每次运行这三个命令。 这样做会起作用,但是不能很好地适应大量的软件(比如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)
用npat
replace$(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
这是更容易打字,更可读。
注意到
- 我们仍然明确指出每个对象文件和最终可执行文件的依赖关系
- 我们必须明确地input两个源文件的编译规则
隐式和模式规则
我们通常会期望所有的c ++源文件都应该以同样的方式处理,并且提供三种方式来陈述它
- 后缀规则(在GNU make中被认为是过时的,但为了向后兼容)
- 隐含的规则
- 模式规则
隐含的规则是内置的,一些将在下面讨论。 模式规则以类似的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
注意到
- 源文件不再有任何依赖关系!
- 有一些与.depend和depend有关的奇怪的魔法
- 如果你做了,那么
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.c
和tool
)。 - 默认情况下,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