由于这是针对 Unix 的,因此可执行文件没有任何扩展名。
需要注意的是root-config 是一个提供正确编译和链接标志的实用程序;以及用于针对 root 构建应用程序的正确库。这只是与本文档的原始受众相关的一个细节。
让我成为婴儿
或者你永远不会忘记你第一次被制造的时候
关于make的介绍性讨论,以及如何编写一个简单的makefile
什么是 Make?我为什么要关心?
名为Make 的工具是一个构建依赖管理器。也就是说,它负责了解需要以什么顺序执行哪些命令,以便从源文件、目标文件、库、头文件等的集合中获取您的软件项目——其中一些可能已经改变最近——并将它们变成正确的最新版本的程序。
其实,你也可以用 Make 做其他事情,不过我不打算谈这个了。
一个简单的 Makefile
假设你有一个目录包含:tooltool.cctool.osupport.ccsupport.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。它将自动执行上面显示的三个步骤。
此处未缩进的行具有 "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 替换所有 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 $(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 提供了三种方式来说明这一点:
- 后缀规则(在 GNU make 中被认为已过时,但为了向后兼容而保留)
- 隐式规则
- 模式规则
内置了隐式规则,下面将讨论其中的一些。模式规则以如下形式指定
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
这意味着目标文件是通过运行显示的命令从 C 源文件生成的,其中“自动”变量 $< 扩展为第一个依赖项的名称。
内置规则
Make 有很多内置规则,这意味着很多时候,一个项目实际上可以通过一个非常简单的 makefile 来编译。
C 源文件的 GNU make 内置规则就是上面展示的规则。同样,我们使用 $(CXX) -c $(CPPFLAGS) $(CFLAGS) 之类的规则从 C++ 源文件创建目标文件。
使用$(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 时,它使用文件中找到的第一个目标(在本例中为全部),但您也可以将目标命名为 get,这就是使 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 有一些奇怪的魔法
- 如果您执行
make 然后ls -A,您会看到一个名为.depend 的文件,其中包含看起来像制作依赖行的内容
其他读物
了解错误和历史记录
Make 的输入语言对空格敏感。特别是,依赖项之后的操作行必须以制表符开头。但是一系列空格看起来是一样的(确实有些编辑器会默默地将制表符转换为空格,反之亦然),这会导致 Make 文件看起来正确但仍然无法工作。这在早期被确定为一个错误,但 (the story goes) 并没有修复,因为已经有 10 个用户。
(这是从我为物理研究生写的 wiki 帖子中复制的。)