为什么我们用Makefile而不是shell脚本描述构建过程?

时间:2014-12-04 11:40:58

标签: shell build makefile bsdmake

  

备注这是user4076675采用的问题“What is the purpose of linking object files separately in a Makefile?”的变体   略有不同的观点。另请参阅相应的META discussion

让我们考虑C项目的经典案例。 gcc编译器 能够一步编译和链接程序。我们可以轻松地 用shell脚本描述构建例程:

case $1 in
    build)  gcc -o test *.c;;
    clean)  rm -f test;;
esac
# This script is intentionally very brittle, to keep
# the example simple.

但是,描述构建过程似乎是惯用的 使用Makefile,包含编译每个编译的额外步骤 单元到目标文件并最终链接这些文件。该 相应的GNU Makefile将是:

.PHONY: all

SOURCES=$(wildcard *.cpp)
OBJECTS=$(SOURCES:.cpp=.o)

%.o: %.cpp
    g++ -c -o $@ $<

all: default
default: $(OBJECTS)
    g++ -o test $^

clean:
    rm -rf *.o

这个第二个解决方案比简单的shell更容易参与 我们之前写的脚本。这也是一个缺点,因为它混乱了 带有目标文件的源目录。那么,为什么我们要描述构建 使用Makefile而不是shell脚本的程序?在手中 在前面的例子中,它似乎是一种无用的复杂功能。

1 个答案:

答案 0 :(得分:2)

在我们编译和链接三个中等大小的简单情况下 文件,任何方法都可能同样令人满意。我会 因此,考虑一般情况,但使用的许多好处 Makefile只对大型项目很重要。一旦我们学会了 最好的工具,让我们掌握复杂的案例,我们想要使用 它也是在简单的情况下。

让我强调一下“好处”&#39;&#39;&#39;使用make而不是简单的 用于编译作业的shell脚本。但首先,我想做一个 无害的观察。

shell脚本的过程范例对于类似编译的作业

是错误的

编写Makefile类似于编写带有轻微的shell脚本 改变观点。在shell脚本中,我们描述了一个过程 问题的解决方案:我们可以开始描述整个过程 使用未定义函数的非常抽象的术语,我们对此进行了改进 描述直到我们达到最基本的描述, 其中一个过程只是一个普通的shell命令。在Makefile中,我们这样做 不介绍任何类似的抽象,但我们专注于我们的文件 想要生产以及我们如何生产它们。这很好用,因为 在UNIX中,一切都是文件,因此每个处理都是 由程序完成,该程序从输入读取其输入数据 文件,做一些计算并在一些输出中写入结果 文件

如果我们想要计算复杂的东西,我们必须使用很多 由输出用作的程序处理的输入文件 输入其他程序,依此类推,直到我们完成决赛 包含我们结果的文件。如果我们翻译计划准备我们的 最终文件放入shell脚本中的一堆程序,然后 处理的当前状态是隐式:计划执行者 知道“它在哪里”,因为它正在执行给定的程序, 这隐含地保证了这样和那样的计算 已经完成,也就是说,这样的中间文件是 已经准备好了现在,哪些数据描述了“计划执行者的位置 在“?

无害的观察 描述“计划所在地的数据” 执行者在“恰好是中间文件的集合 已经准备好了,这正是所做的数据 在我们编写Makefile时显式。

这种无害的观察实际上是概念上的差异 shell脚本和Makefile之间解释了所有优点 Makefile在编译作业和类似作业中的shell脚本上。 当然,要充分体会这些优势,我们必须写出来 正确 Makefile,这对初学者来说可能很难。

Make可以轻松地继续执行

处的中断任务

当我们使用Makefile描述编译作业时,我们可以轻松地完成 中断并稍后恢复。这是一个结果 无害的观察。类似的效果只能通过以下方式实现 在shell脚本中做了大量工作,而它只是内置的 make

Make可以轻松使用项目的多个版本

您发现Makefile会使对象混乱源树 文件。但Makefile实际上可以参数化来存储这些 目标文件在专用目录中。我与BSD Owl合作 的宏并使用

MAKEOBJDIR='/usr/home/michael/obj${.CURDIR:S@^/usr/home/michael@@}'

以便所有目标文件都在~/obj下结束,并且不会污染我的 源。看到这个 answer 了解更多详情。

高级Makefile允许我们同时拥有多个目录 包含具有不同编译的项目的多个构建 选项。例如,启用或调试不同的功能 版本等。这也是无害观察的结果 Makefiles实际上围绕着一组中间人 文件。 testsuite of BSD Owl

中说明了这种技术

Make可以轻松实现构建的并行化

我们可以轻松地并行构建程序,因为这是一个标准 make的许多版本的功能。这也是后果 无害的观察:因为“计划执行者在哪里”是一个 在Makefile中显式数据,make可以推理 它。在shell脚本中实现类似的效果需要a 努力。

任何版本的make的并行模式只有在以下情况下才能正常工作 正确指定了依赖性。这可能是相当的 很难实现,但具有 功能 从字面上来说,这是一个问题。它被称为 META模式。它 使用编译作业的第一个非并行传递进行计算 通过监视文件访问来实现实际的依赖关系,并使用它 以后并行构建中的信息。

Makefile易于扩展

由于特殊的观点 - 也就是另一种结果 无害观察 - 用于编写Makefile,我们可以 通过挂钩构建系统的所有方面,可以轻松扩展它们。

例如,如果我们决定所有数据库I / O样板代码 应该用自动工具编写,我们只需写在 Makefile自动工具应该将哪些文件用作写入的输入 样板代码。没有更少,仅此而已。我们可以添加这个 描述几乎我们喜欢的地方,make会得到它 无论如何。在shell脚本构建中执行此类扩展即可 比必要的更难。

这种可扩展性易用性是Makefile代码重用的一个很好的动力。