发布
2012年12月17日 (最后更新: 2012年12月17日)

Make

评分: 3.7/5 (185 票)
*****

简介

Make 是一个用于构建其他程序或文档的程序。Make 适用于任何具有可能完成或未完成的中间步骤的过程。

Make 不会重新构建已更新的项目。此外,它知道如何构建中间目标。您可以使用 Make 文件来指示 Make。

Make 程序不止一个。C/C++ 开发人员最常遇到的是 GNU Make。我将首先用示例描述 GNU Make,然后介绍 BSD Make,最后对它们之间的区别进行一些评论。

GNU Make 入门

GNU Make 内置了许多规则。它们被称为内置规则。让我们看一个示例,hello.c
1
2
3
4
5
6
7
#include <stdio.h>

int main()
{
	printf("hello world\n:");
	return 0;
}


要构建程序,只需键入
make hello
它会运行
cc     hello.c   -o hello

因此,GNU Make 已知道可以从 .c 文件构建程序,并且知道如何编译和链接它。

让我们再试一个,这次包含更多文件。这次我们需要一个 makefile。GNU Make 文件可以命名为 GNUmakefile、Makefile 或 makefile。在这些示例中我将使用 GNUmakefile,因为稍后我们将介绍 BSD Make。
calc.c
1
2
3
4
5
6
7
8
#include <stdio.h>
#include "engine.h"

int main()
{
	printf("%d\n", calc("2+3"));
	return 0;
}


engine.h
1
2
3
#pragma once

int calc(const char* expression);


engine.c
1
2
3
4
5
6
#include "engine.h"

int calc(const char *expression)
{
	return 5;
}


GNUmakefile
1
2
3
4
5
OBJ = calc.o engine.o

calc: $(OBJ)

$(OBJ): engine.h


我们定义了一个宏 OBJ,它包含我们的目标文件列表。请记住,GNU Make 知道如何从 C 程序创建目标文件。

接下来,我们告诉 GNU Make 我们的程序 calc 由这些 OBJ 文件组成。当我们运行 make 时,它会执行
cc    -c -o calc.o calc.c
cc    -c -o engine.o engine.c
cc   calc.o engine.o   -o calc

最后,我们告诉 GNU Make,如果 engine.h 发生更改,我们必须重新编译目标文件。这称为依赖项。

现在,我们来介绍 C++。让我们定义一个新文件:calcpp.cc
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
extern "C"
{
#include "engine.h"
}

int main()
{
	std::cout << calc("2+3") << std::endl;
	return 0;
}


这是我们修改后的 GNUmakefile
1
2
3
4
5
6
OBJ = calcpp.o engine.o

calc: $(OBJ)
	$(LINK.cc) $^ -o $@

$(OBJ): engine.h


那么有什么改变?我们需要告诉 GNU Make 我们正在链接一个 C++ 程序。我们使用 GNU Make 宏 LINK.cc 来告诉它我们正在链接一个 C++ 程序。

$^ 表示所有依赖项,在此情况下为 calcpp.o 和 engine.o。

$@ 表示我们正在构建的目标,在此情况下为 calc。

当我们运行 make 时,它会执行
c++    -c -o calcpp.o calcpp.cc
c++     calcpp.o engine.o -o calc

那编译 engine.c 呢?嗯,我们之前已经编译过它了,make 知道它不需要重新编译。

BSD Make

BSD Make 早于 GNU Make,并且它有所不同。BSD Make 没有很多内置规则。它需要在 make 文件中被告知很多东西。正如您在上面看到的,GNU Make 知道很多。

BSD Make 知道依赖项和构建这些依赖项的规则,但它没有所有那些内置的默认规则,因此需要被告知。一个使用 BSD Make 的系统,而不是被告知所有内容,而是提供一组包含文件,这些文件定义了在该系统上如何完成事物。例如,在 FreeBSD 上,如果您想编写程序,则包含 <bsd.prog.mk>。这包含了编译、安装、卸载、归档……开发程序所需的所有规则。

GNU Make 和 BSD Make 的比较

请记住,GNU 是自由软件基金会的工具集。GNU 的意思是 GNU 不是 Unix。这很重要。当 GNU 项目启动时,它的目标是提供 Unix 程序的免费可用替代品。并且它们通过添加有用的、一致的行为来改进它们。

BSD Make 只知道依赖项、目标、规则和宏。GNU Make 将内置规则添加到其中,以便为开发人员提供便利。

事实证明,系统的细节是冲突的,并且无法提前知道。BSD Make 通过让系统定义其参数来处理系统依赖项。GNU Make 的内置规则被证明是不够的。Auto Tools 项目(AutoConf、AutoMake、LibTools)是为了弥补这一点而开发的。它通过在配置时发现系统来工作,然后生成 makefiles。

我更喜欢 BSD 的解决方案。很明显,在这个例子中它的设计更好。我发现 BSD 通常都是如此。

示例

此示例使用了 3 个 C++ 源文件(main.cc、config.cc、dispatch.cc)和 2 个头文件(config.hpp、dispatch.hpp)。我没有添加依赖项,在现实世界中,它们是自动生成的。

GNUmakefile
1
2
3
4
5
6
CXXFLAGS = -g

all: goodlistener

goodlistener: main.o config.o dispatch.o
	$(LINK.cc) -o $@ $^


BSDmakefile
1
2
3
4
5
6
7
PROG_CXX = goodlistener
SRCS = main.cc config.cc dispatch.cc
MAN = goodlistener.1
MAKEOBJDIR = work
CXXFLAGS=-g

.include <bsd.prog.mk>