make
make 是一种用于构建应用程序的实用程序。本教程将教你如何使用 Makefile 与此实用程序一起使用。本教程主要关注 GNU make。
你可能会认为 Make 仅仅是一个构建大型二进制文件或库的工具(它确实如此,几乎到了极致),但它远不止于此。
Makefile 是机器可读的文档,可以使你的工作流程可重现。
– Mike Bostock , "为什么要使用 Make"[1]
在我们开始讨论“make”之前,让我们首先描述很久以前使用的构建过程:“手动编译”,也称为“手动构建”。如果你只有一个或两个源文件,这个过程并不算太糟糕。一个简单的编译过程可以通过手动输入以下代码来完成
g++ -o file file.cpp
对于初学者来说,这可能需要一段时间才能习惯。作为替代方案,使用 make 命令将是
make file
这两个命令都会做同样的事情 - 获取 file.cpp 源文件,编译它,然后将其链接到可执行文件。
但问题在于,当用户必须使用多个源文件或依赖于库时。创建一个 SDL 应用程序可能包含以下内容
g++ `sdl-config --cflags` -o file file.cpp
这越来越难记了,不是吗?
让我们添加一些优化
g++ -O3 -fomit-frame-pointer `sdl-config --cflags` -o file file.cpp
你真的想每次对源文件进行最小的编辑时都输入整个命令行吗?这就是 make 的用武之地 - 它将节省你的时间!
一个 Makefile 就像一个简单的依赖树 - 它编译过时的内容,然后将你的软件链接在一起。你将必须指定编译选项,但它们不会再让你那么费力了。一个用于编译应用程序的简单 Makefile
Makefile
default: myapp
myapp:
g++ -o file file.cpp
要编译你的应用程序,你可以执行
make
或者
make myapp
好吧,你现在可能会问,这有什么用?我自己可以为此编写一个 shell 脚本!所以现在让我们处理多个文件和目标文件 - 你的应用程序足够大了,可以链接了:它包含五个文件。
全局来看,存在三个函数
int main(); (file1.cpp) void file2function(std::string from); (file2.cpp) void file3function(std::string from); (file3.cpp)
File2 和 file3 有它们的头文件,因此它们可以相互链接。实际的应用程序将如下所示
file1.cpp
#include "file2.h"
#include "file3.h"
int main()
{
file2function("main");
file3function("main");
}
file2.h
#include <string>
#include <iostream>
#include "file3.h"
void file2function(std::string source);
file2.cpp
#include "file2.h"
void file2function(std::string from)
{
std::cout << "File 2 function was called from " << from << std::endl;
file3function("file2");
}
file3.h
#include <string>
#include <iostream>
#include "file2.h"
void file3function(std::string source);
file3.cpp
#include "file3.h"
void file3function(std::string from)
{
std::cout << "File 3 function was called from " << from << std::endl;
}
那么,我该如何将这一切链接起来呢?好吧,最基本的方法是手动完成所有操作
g++ -c -o file2.o file2.cpp
g++ -c -o file3.o file3.cpp
g++ -o file1 file1.cpp file2.o file3.o
但是,make 提供了一种更简单的方法,**Makefile**
Makefile
default: testcase
testcase: file2.o file3.o
g++ -o file1 file1.cpp file2.o file3.o
非常不同,对吧?有一种不同的方法来做到这一点,这就是使 make 如此优秀的原因
Makefile
# Create the executable "file1" from all the source files.
COMPILER=g++
OBJS=file1.o file2.o file3.o
default: testcase
testcase: $(OBJS)
$(COMPILER) -o file1 $(OBJS)
现在扩展起来有多容易?只需将一个文件添加到 OBJS 列表中,你就完成了!
这是一个完整的 Makefile,它处理将多个源文件(并创建一堆“.o”文件作为中间步骤)转换为最终的“file1”可执行文件的完整过程。
优秀的程序员会在 Makefile 中添加人类可读的注释(以“#”字符开头的行),以描述编写 Makefile 的原因以及 make 预期如何使用此 Makefile(这,唉,并不总是实际发生的情况)。
Makefile 中的一些语句可能非常长。为了使它们更容易阅读,一些程序员将长语句分解成几个更短、更容易阅读的物理行。许多控制台横向显示 80 个字符,这是某些程序员用于分解行和注释的阈值。
简而言之,程序员可能会认为编写几个短的物理行来组成一个语句看起来更漂亮,例如
some really long line \
with, like, \
a bajillion parameters and \
several file names \
and stuff
而不是将相同的语句塞进一行中
some really long line with, like, a bajillion parameters and several file names and stuff
当你在命令提示符下键入“make clean”时,“make”会删除你指定的所有易于替换的文件 - 即从你不可替换的手写源文件生成的中间文件和输出文件。
要告诉“make”某个文件是易于替换的文件,请将其添加到 Makefile 中“clean”部分的“rm”行中,并且每次你在命令提示符下键入“make clean”时,该文件都将被删除。(如果你的 Makefile 中还没有“clean”部分,请在 Makefile 的末尾添加一个如下所示的部分)
.PHONY: clean
clean:
rm *.o
rm file
在此示例中,我们告诉“make”所有中间目标文件(“*.o”)和最终的可执行文件(“file”)都是安全删除、易于重新生成的。通常,人们(或 automake 脚本)会设置清理步骤以删除 Makefile 中的每个目标。[2]
通常,程序员会定期运行“make clean
”,然后存档目录中的*每个*文件,然后运行“make”以重新生成每个文件。然后他测试程序可执行文件,确信他正在测试的程序可以很容易地完全从存档中的文件重新生成。
许多 Makefile 包含一个“make check
”目标,该目标执行自检(如果有)。[3](make test
目标通常是 make check
的同义词)。[4]
为了支持测试优先编程和回归测试,“make check
”只运行测试,它不会重建程序;但通常 make all
首先重新构建程序,然后也运行“make check”。
“make”接受三种设置变量的方式。
- 递归扩展变量 通过使用
variable = value
定义。这意味着如果一个变量 'y' 包含对另一个变量 'x' 的引用,并且 'x' 在 'y' 定义后发生更改,则 'y' 将包含对 'x' 进行的更改。
x = abc
y = $(x)ghi
x = abcdef
# x will be abcdef, while y will be abcdefghi
- 简单扩展变量 通过使用
variable := value
定义。这意味着如果一个变量 'x' 包含对另一个变量 'y' 的引用,则将对 'y' 变量进行一次且仅一次扫描。
x := abc
y := $(x)ghi
# x is 'abc' at this time
x := abcdef
# x will be abcdef, while y will be abcghi
- 如果你只想确保设置了变量,则可以使用
variable ?= value
。如果变量已设置,则不会运行定义;否则,将 'variable' 设置为 'value'。[5]
许多程序员建议不要创建包含空格或美元符号的文件,以避免“make”和许多其他实用程序中的“文件名中包含空格”错误。[6]
如果你必须处理文件名中包含空格或美元符号的文件(例如文件“my $0.02 program.c”),有时你可以使用双反斜杠和双美元符号转义文件的字面名称 - 在 Makefile 中,该文件名可以表示为“my\\ $$0.02\\ program.c”。[7]
遗憾的是,当“make”与文件列表一起工作时(这些文件在内部表示为以空格分隔的文件名),双斜杠转义不起作用。一种解决方法是使用无空格的表示法(如“my+$$0.02+program.c”)来引用该文件名,然后在makefile中稍后使用$(subst)函数将该无空格表示法转换回实际的磁盘文件名。[6] 遗憾的是,这种解决方法无法处理同时包含空格和加号的文件名。
也许最简单的办法是避免在“make”的输入或输出中使用包含空格的文件名。
创建新的makefile时,将其命名为“Makefile
”(8个字符,无扩展名)。
原则上,您可以创建具有其他名称的makefile,例如makefile
或GNUmakefile
,这些名称(如Makefile
)会被GNU版本的make
实用程序自动找到,或者您可以将其他名称与--file
选项一起传递给make实用程序。[8]
- ↑ Mike Bostock "为什么要使用Make".2013。
- ↑ "Makefile和RMarkdown". 2015。
- ↑ "用户标准目标"。
- ↑ "实现`make check`或`make test`"。
- ↑ "GNU Make手册" “6.2 两种变量”章节。
- ↑ a b John Graham-Cumming. "GNU Make遇到包含空格的文件名". CM Crossroads 2007。
- ↑ John Graham-Cumming. "GNU Make转义:狂野之旅" CM Crossroads 2007。
- ↑ "GNU Make手册" “3.2 为Makefile指定什么名称”章节。
- "问答先生"系列关于GNU Make的文章
- make有什么问题?
- GNU make有什么问题?
- Peter Miller. "递归Make有害". (曾为 递归Make有害)
- 高级自动依赖项生成.
- "Kleknev:构建系统的粗粒度分析器". 有时“make”用于构建极其庞大而复杂的系统;Kleknev系统帮助人们找出构建过程中哪些项目消耗了最多的时间。