Makefile教程1 快速入门

络腮胡菲菲  金牌会员 | 2024-1-13 04:28:21 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 919|帖子 919|积分 2757

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开始:
  1. hello:
  2.         echo "Hello, World"
复制代码
注意:Makefile必须使用TAB缩进,不能使用空格,否则make将失败。
以下是运行上述示例的输出:
  1. $ make
  2. echo "Hello, World"
  3. Hello, World
复制代码
1.5 Makefile语法

生成文件语法
Makefile 由一组规则组成。 规则通常如下所示:
  1. targets: prerequisites
  2.         command
  3.         command
  4.         command
复制代码

  • targets是文件名,以空格分隔。 通常每条规则只有一个。
  • 这些command是通常用于创建目标的一系列步骤。
  • prerequisites 也是文件名,以空格分隔。 在运行target的命令之前,这些文件需要存在。 这些也称为依赖项
1.6 Make的本质

让我们从hello world示例开始:
  1. hello:
  2.         echo "Hello, World"        echo "This line will print if the file hello does not exist."
复制代码

  • 我们有一个名为 hello 的目标
  • 该目标有两个命令
  • 该目标没有先决条件
然后我们将运行 make hello。 只要hello文件不存在,命令就会运行。 如果hello存在,则不会运行任何命令。
让我们创建更典型的Makefile:编译单个C文件。
blah.c
  1. int main() { return 0; }
复制代码
然后创建 Makefile(一如既往地称为 Makefile):
  1. blah:
  2.         cc blah.c -o blah
复制代码
运行make,由于没有将目标作为参数提供给make命令,因此将运行第一个目标。 在这种情况下,只有一个目标(blah)。 第一次运行它时,将会创建blah。 第二次,你会看到“make: 'blah' is up to date”。 那是因为blah文件已经存在。 但有一个问题:如果我们修改blah.c 然后运行make,则不会重新编译任何内容。
我们通过添加先决条件来解决这个问题:
  1. blah: blah.c
  2.         cc blah.c -o blah
复制代码
当我们再次运行make 时,会发生以下步骤:

  • 选择第一个目标,因为第一个目标是默认目标
  • 这有blah.c的先决条件
  • Make决定是否应该运行blah目标。 仅当blah不存在或blah.c比 blah新时才会运行
最后一步很关键,也是make的精髓。它试图做的是确定自上次编译blah以来blah的先决条件是否发生了变化。也就是说,如果blah.c被修改,运行make应该重新编译该文件。 相反,如果blah.c没有更改,则不应重新编译它。
为了实现这一点,它使用文件系统时间戳来确定是否发生了更改。 这是一个合理的启发式方法,因为文件时间戳通常仅在文件被修改时才会更改。 但情况并非总是如此。 例如,您可以修改文件,然后将该文件的修改时间戳更改为旧的时间戳。 如果这样做,Make会错误地猜测该文件没有更改,因此可以被忽略。
唷,真是拗口啊。 确保您理解这一点。 这是 Makefile 的关键,您可能需要几分钟才能正确理解。 如果事情仍然令人困惑,请尝试上面的示例或观看上面的视频。
参考资料

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程序
  1. blah: blah.o
  2.         cc blah.o -o blah # Runs third
  3. blah.o: blah.c
  4.         cc -c blah.c -o blah.o # Runs second
  5. # Typically blah.c would already exist, but I want to limit any additional required files
  6. blah.c:
  7.         echo "int main() { return 0; }" > blah.c # Runs first
复制代码
如果删除blah.c,所有三个目标都将重新运行。如果您运行touch blah.o (从而将时间戳更改为比 blah 更新),则只有第一个目标会运行。如果您不进行任何更改,则所有目标都不会运行。
下一个示例没有做任何新内容,但仍然很好的补充示例。它将始终运行两个目标,因为some_file依赖于other_file,而other_file从未创建。
  1. some_file: other_file
  2.         echo "This will always run, and runs second"
  3.         touch some_file
  4. other_file:
  5.         echo "This will always run, and runs first"
复制代码
1.7 Make clean

clean经常被用作删除其他目标输出的目标,你可以运行make和make clean来创建和删除some_file。
clean在这里做了两件新事情:

  • 它不是第一目标(默认),也不是先决条件。这意味着除非你明确调用 make clean,否则它永远不会运行。
  • 它不是一个文件名。如果你碰巧有一个名为clean的文件,这个目标就不会运行,这不是我们想要的。
  1. some_file:
  2.         touch some_file
  3. clean:
  4.         rm -f some_file
复制代码
1.8 变量

变量只能是字符串。通常要使用 :=,但 = 也可以。
下面是一个使用变量的示例:
  1. files := file1 file2
  2. some_file: $(files)
  3.         echo "Look at this variable: " $(files)
  4.         touch some_file
  5. file1:
  6.         touch file1
  7. file2:
  8.         touch file2
  9. clean:
  10.         rm -f file1 file2 some_file
复制代码
单引号或双引号对Make没有任何意义。它们只是分配给变量的字符。不过,引号对shell/bash很有用,在printf等命令中需要用到它们。在本例中,两个命令的行为是一样的:
  1. a := one two # a is set to the string "one two"
  2. b := 'one two' # Not recommended. b is set to the string "'one two'"
  3. all:
  4.         printf '$a'
  5.         printf $b
复制代码
使用 ${} 或 $() 引用变量
  1. x := dude
  2. all:
  3.         echo $(x)
  4.         echo ${x}
  5.         # Bad practice, but works
  6.         echo $x
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表