跳转到内容

make

0% developed
来自 Wikibooks,开放世界中的开放书籍
(重定向自 Makefile)

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 的用武之地——它将节省你的时间!

基础知识

[编辑 | 编辑源代码]

一个 make 文件作为一个简单的依赖树工作——它编译过时的内容,然后将你的软件链接在一起。你将不得不指定编译选项,但它们不会再对你那么苛刻了。一个用于编译应用程序的简单 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,它处理将多个源文件(并创建一个 bunch of ".o" 文件作为中间步骤)转换为最终的 "file1" 可执行文件的完整过程。

优秀的程序员在 makefile 中添加人类可读的注释(以“#”字符开头的行),以描述编写 makefile 的原因以及make打算如何使用此 makefile(可惜,这并不总是实际发生的情况)。

高级 make

[编辑 | 编辑源代码]

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 clean”时,“make”会删除你指定的所有易于替换的文件——即从你的不可替换的手写源文件生成的中间文件和输出文件。

要告诉“make”某个文件是易于替换的文件,请将其添加到 makefile 中“clean”部分的“rm”行,并且每次你在命令提示符下键入“make clean”时,该文件都将被删除。(如果你的 makefile 中还没有“clean”部分,请在 makefile 的末尾添加一个如下所示的部分)

.PHONY: clean
clean:
	rm *.o
	rm file

在这个例子中,我们告诉“make”所有中间目标文件(“*.o”)和最终的可执行文件(“file”)都是安全的、易于重新生成的。通常,人们(或 automake 脚本)会设置 clean 步骤以删除 makefile 中的每个目标。[2]

通常,程序员会定期运行“make clean”,然后存档目录中的每个文件,然后运行“make”以重新生成每个文件。然后他测试程序可执行文件,确信他正在测试的程序可以很容易地完全从存档中的文件重新生成。

make check

[编辑 | 编辑源代码]

许多 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 时,请将其命名为“Makefile”(8 个字符,无扩展名)。

原则上,您可以创建具有其他名称的 makefile,例如 makefileGNUmakefile,它们(像 Makefile 一样)会被 GNU 版本的 make 实用程序自动找到,或者您可以使用 --file 选项将其他名称传递给 make 实用程序。[8]

Makefile 示例

[编辑 | 编辑源代码]

参考文献

[编辑 | 编辑源代码]
  1. Mike Bostock "为什么要使用 Make".2013.
  2. "Makefile 和 RMarkdown". 2015.
  3. "用户标准目标".
  4. "实现 `make check` 或 `make test`".
  5. "GNU Make 手册" “6.2 变量的两种类型”部分。
  6. a b John Graham-Cumming. "GNU Make 遇到文件名中包含空格". CM Crossroads 2007.
  7. John Graham-Cumming. "GNU Make 转义:在狂野中漫步" CM Crossroads 2007.
  8. "GNU Make 手册" “3.2 为您的 Makefile 指定什么名称”部分。

进一步阅读

[编辑 | 编辑源代码]

书籍和手册

[编辑 | 编辑源代码]
华夏公益教科书