Makefile教程1 快速入门
1 快速入门
1.1 为什么存在 Makefile?
Makefile用于帮助决定大型程序的哪些部分需要重新编译。在绝大多数情况下,都会编译C或C++文件。 其他语言通常有自己的工具,其用途与Make类似。当您需要根据已更改的文件运行一系列指令时,Make也可以在编译之外使用。 本教程将重点介绍C/C++编译。
下面是您可以使用Make构建的示例依赖关系图。如果任何文件的依赖项发生更改,则该文件将被重新编译:
1.2 Make有哪些替代?
流行的C/C++替代构建系统有SCons、CMake、Bazel 和 Ninja。 一些代码编辑器(例如 Microsoft Visual Studio)有自己的内置构建工具。 对于Java,有Ant、Maven和Gradle。 其他语言(例如 Go、Rust 和 TypeScript)都有自己的构建工具。
Python、Ruby 和Javascript等解释性语言不需要与Makefile之类的东西。 Makefile的目标是根据已更改的文件来编译需要编译的任何文件。 解释语言中的文件发生更改时,不需要重新编译任何内容。 程序运行时,将使用该文件的最新版本。
1.3 Make的版本和类型
Make有多种实现,但本指南的大部分内容都适用于您使用的任何版本。 然而,它是专门为GNU Make编写的,GNU Make是Linux和MacOS上的标准实现。 所有示例都适用于Make版本3和4,除了一些个别差异之外,它们几乎相同。
1.4 运行示例
要运行这些示例,您需要一个终端并安装“make”。 对于每个示例,将内容放入名为Makefile的文件中,然后在该目录中运行命令make。 让我们从最简单的Makefile开始:
hello:
echo "Hello, World"
注意:Makefile必须使用TAB缩进,不能使用空格,否则make将失败。
以下是运行上述示例的输出:
$ make
echo "Hello, World"
Hello, World
1.5 Makefile语法
生成文件语法
Makefile 由一组规则组成。 规则通常如下所示:
targets: prerequisites
command
command
command
- targets是文件名,以空格分隔。 通常每条规则只有一个。
- 这些command是通常用于创建目标的一系列步骤。
- prerequisites 也是文件名,以空格分隔。 在运行target的命令之前,这些文件需要存在。 这些也称为依赖项
1.6 Make的本质
让我们从hello world示例开始:
hello:
echo "Hello, World"
echo "This line will print if the file hello does not exist."
- 我们有一个名为 hello 的目标
- 该目标有两个命令
- 该目标没有先决条件
然后我们将运行 make hello。 只要hello文件不存在,命令就会运行。 如果hello存在,则不会运行任何命令。
让我们创建更典型的Makefile:编译单个C文件。
blah.c
int main() { return 0; }
然后创建 Makefile(一如既往地称为 Makefile):
blah:
cc blah.c -o blah
运行make,由于没有将目标作为参数提供给make命令,因此将运行第一个目标。 在这种情况下,只有一个目标(blah)。 第一次运行它时,将会创建blah。 第二次,你会看到“make: 'blah' is up to date”。 那是因为blah文件已经存在。 但有一个问题:如果我们修改blah.c 然后运行make,则不会重新编译任何内容。
我们通过添加先决条件来解决这个问题:
blah: blah.c
cc blah.c -o blah
当我们再次运行make 时,会发生以下步骤:
- 选择第一个目标,因为第一个目标是默认目标
- 这有blah.c的先决条件
- Make决定是否应该运行blah目标。 仅当blah不存在或blah.c比 blah新时才会运行
最后一步很关键,也是make的精髓。它试图做的是确定自上次编译blah以来blah的先决条件是否发生了变化。也就是说,如果blah.c被修改,运行make应该重新编译该文件。 相反,如果blah.c没有更改,则不应重新编译它。
为了实现这一点,它使用文件系统时间戳来确定是否发生了更改。 这是一个合理的启发式方法,因为文件时间戳通常仅在文件被修改时才会更改。 但情况并非总是如此。 例如,您可以修改文件,然后将该文件的修改时间戳更改为旧的时间戳。 如果这样做,Make会错误地猜测该文件没有更改,因此可以被忽略。
唷,真是拗口啊。 确保您理解这一点。 这是 Makefile 的关键,您可能需要几分钟才能正确理解。 如果事情仍然令人困惑,请尝试上面的示例或观看上面的视频。
参考资料
- 软件测试精品书籍文档下载持续更新 https://github.com/china-testing/python-testing-examples 请点赞,谢谢!
- 本文涉及的python测试开发库 谢谢点赞! https://github.com/china-testing/python_cn_resouce
- python精品书籍下载 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
- Linux精品书籍下载 https://www.cnblogs.com/testing-/p/17438558.html
1.6 更多示例
以下Makefile最终运行所有三个目标。 当您在终端中运行make时,它将通过一系列步骤构建名为blah的程序:
- Make选择目标blah,因为第一个目标是默认目标
- blah需要blah.o,因此搜索blah.o目标
- blah.o需要blah.c,因此搜索blah.c目标
- blah.c 没有依赖项,因此运行echo命令
- 然后运行 cc -c 命令,因为所有blah.o依赖项都已完成
- 运行顶部cc命令,因为所有blah依赖都完成了
- 最终:blah是已编译的c程序
blah: blah.o
cc blah.o -o blah # Runs third
blah.o: blah.c
cc -c blah.c -o blah.o # Runs second
# Typically blah.c would already exist, but I want to limit any additional required files
blah.c:
echo "int main() { return 0; }" > blah.c # Runs first
如果删除blah.c,所有三个目标都将重新运行。如果您运行touch blah.o (从而将时间戳更改为比 blah 更新),则只有第一个目标会运行。如果您不进行任何更改,则所有目标都不会运行。
下一个示例没有做任何新内容,但仍然很好的补充示例。它将始终运行两个目标,因为some_file依赖于other_file,而other_file从未创建。
some_file: other_file
echo "This will always run, and runs second"
touch some_file
other_file:
echo "This will always run, and runs first"
1.7 Make clean
clean经常被用作删除其他目标输出的目标,你可以运行make和make clean来创建和删除some_file。
clean在这里做了两件新事情:
- 它不是第一目标(默认),也不是先决条件。这意味着除非你明确调用 make clean,否则它永远不会运行。
- 它不是一个文件名。如果你碰巧有一个名为clean的文件,这个目标就不会运行,这不是我们想要的。
some_file:
touch some_file
clean:
rm -f some_file
1.8 变量
变量只能是字符串。通常要使用 :=,但 = 也可以。
下面是一个使用变量的示例:
files := file1 file2
some_file: $(files)
echo "Look at this variable: " $(files)
touch some_file
file1:
touch file1
file2:
touch file2
clean:
rm -f file1 file2 some_file
单引号或双引号对Make没有任何意义。它们只是分配给变量的字符。不过,引号对shell/bash很有用,在printf等命令中需要用到它们。在本例中,两个命令的行为是一样的:
a := one two # a is set to the string "one two"
b := 'one two' # Not recommended. b is set to the string "'one two'"
all:
printf '$a'
printf $b
使用 ${} 或 $() 引用变量
x := dude
all:
echo $(x)
echo ${x}
# Bad practice, but works
echo $x