Makefile中的.PHONY的目的是什么?

2485
在Makefile中,.PHONY是什么意思?我已经阅读过这篇文章,但是太复杂了。请有人用简单的语言来解释一下吗?
10个回答

2823

默认情况下,Makefile 目标是“文件目标” - 它们用于从其他文件构建文件。Make 假定其目标是一个文件,这使得编写 Makefile 相对容易:

foo: bar
  create_one_from_the_other foo bar

然而,有时候你想让Makefile运行的命令不代表文件系统中的实际文件。其中"clean"和"all"是很好的例子。虽然这种情况可能不会发生,但你的主目录下可能会有一个名为clean的文件。在这种情况下,Make将感到困惑,因为默认情况下clean目标将被与该文件关联,并且只有当该文件对其依赖项不再具有最新状态时,Make才会运行它。

这些特殊的目标被称为伪目标,你可以明确告诉Make它们不与任何文件相关联,例如:

.PHONY: clean
clean:
  rm -rf *.o

现在,即使您有一个名为 clean 的文件,make clean 命令也将按预期运行。

在 Makefile 中,伪目标是指始终过时的目标,因此每当您执行 make <phony_target> 时,它都会独立于文件系统状态而运行。一些常见的伪目标包括: allinstallcleandistcleanTAGSinfocheck


89
为什么它被称为“phony”?因为它不是一个真正的目标。也就是说,该目标名称不是由该目标的命令生成的文件。 - Bernard
165
@Lazer:我不知道你是否以英语为母语,而我不是。单词 "phony" 不意味着它听起来的那样。http://en.wiktionary.org/wiki/phony 上说:欺诈的;虚假的;具有误导性的外观。 - Bahbar
74
这个答案并不完整,虽然可能在链接的教程中有解释。.PHONY 会强制让 Makefile 中的标签/文件在目标的拓扑排序中被构建。也就是说,如果你有一个“cleanup:”标签被设置为虚拟的,并且你的安装标签被定义为具有先决条件 "cleanup",例如 'install: cleanup',那么当 Makefile 尝试构建 'install' 时,cleanup 标签将始终运行。这对于你总是想要进行的步骤非常有用,无论是否成功都会执行它 - 它将忽略时间戳并强制执行。 - synthesizerpatel
27
请注意,只要您没有与任务名称相同的文件,就不需要使用.PHONY。任务将始终被执行,而Makefile将更加易读。 - Bernard
56
“一个虚假的目标就是一个始终过时的目标” - 说得太好了! - Danijel
显示剩余14条评论

857
假设您有一个名为install的目标,这在Makefile中非常常见。如果您不使用.PHONY,并且与Makefile位于同一目录中存在名为install的文件,则make install无动作。这是因为Make解释该规则为“执行某种配方以创建名为install的文件”。由于文件已经存在,并且其依赖项没有更改,因此什么也不会发生。
然而,如果您将install目标设置为PHONY,则它将告诉make工具该目标是虚构的,并且make不应期望它创建实际文件。因此,它不会检查install文件是否存在,这意味着:a)如果文件确实存在,其行为不会改变, b)不会调用额外的stat()
通常,Makefile中所有不会生成与目标名称相同的输出文件的目标都应该是PHONY。这通常包括allinstallcleandistclean等目标。

11
接受的答案已经从一开始毫无价值到现在得到了显著改进,现在和这个答案一样好。我不得不查看其修订历史记录才能理解你的评论。 - Mark Amery
2
这似乎有点无意义,因为我在我的代码库中永远不会有名为“install”或类似名称的文件。大多数文件都将具有文件扩展名,而没有文件扩展名的文件通常都是大写的,例如“README”。然而,如果您有一个名为“install”的bash脚本而不是“install.sh”,那么您将会遇到麻烦。 - nucleartide
9
@JasonTu 这个说法未必正确。Bash 脚本约定,对于类似有主函数的“程序”,应省略 .sh.bash 扩展名,并将添加扩展名保留给你包含的库(source mylib.sh)。事实上,我能找到这个 Stack Overflow 问题是因为我在与 Makefile 相同的目录中有一个名为 install 的脚本。 - Kyle
14
@Kyle 是的,我不确定我的过去自己的意思是什么。现在我一直使用 .PHONY... - nucleartide
6
这里的解决方案很简单:建造一个时间机器并“替换”你过去的自己。我建议你带上一把铲子,这样没人会意识到你是“伪造”的版本。 - Mateen Ulhaq
显示剩余3条评论

234
注意:make工具会读取makefile并检查规则中“:”符号两边文件的修改时间戳。
示例:
在目录“test”中存在以下文件:
prerit@vvdn105:~/test$ ls
hello  hello.c  makefile

在makefile中,规则的定义如下:
hello:hello.c
    cc hello.c -o hello

现在假设文件'hello'是一个包含一些数据的文本文件,它是在'hello.c'文件之后创建的。因此,'hello'的修改(或创建)时间戳将比'hello.c'的时间戳更新。所以当我们从命令行调用'make hello'时,它将打印如下内容:
make: `hello' is up to date.

现在访问 'hello.c' 文件并在其中添加一些空格,这不会影响代码的语法或逻辑,然后保存并退出。现在,hello.c 的修改时间戳比 'hello' 的时间戳要新。现在,如果你调用 'make hello',它将执行以下命令:
cc hello.c -o hello

文件'hello'(文本文件)将被一个新的二进制文件'hello'(上述编译命令的结果)覆盖。
如果我们在makefile中使用.PHONY如下:
.PHONY:hello

hello:hello.c
    cc hello.c -o hello

然后调用'make hello',它将忽略pwd 'test'中存在的任何文件,并每次执行该命令。
现在假设'hello'目标没有声明任何依赖项:
hello:
    cc hello.c -o hello

如果在当前目录的'test'文件夹中已经存在名为'hello'的文件,那么执行'make hello'命令将始终显示如下内容:
make: `hello' is up to date.

19
这不仅使我运行的命令有意义,而且最终使整个make命令变得有意义,一切都与文件有关!感谢您的答案。 - Kzqai
2
这是一个简单规则的样例:目标: 依赖 ... 命令 ...参考:https://www.gnu.org/software/make/ - VicX

105
.PHONY: install
  • 这意味着在这个Makefile中,“install”这个词不代表一个文件名;
  • 这意味着Makefile与同一目录下名为“install”的文件无关。

56

这是一个构建目标(build target),它不是文件名。


49

特殊的目标.PHONY:允许声明虚拟目标,这样make不会将其作为实际文件名进行检查:即使这些文件仍然存在,它也会一直工作。

您可以在Makefile中放置多个.PHONY:

.PHONY: all

all : prog1 prog2

...

.PHONY: clean distclean

clean :
    ...
distclean :
    ...

声明伪目标的另一种方法是:只需在没有先决条件的情况下放置::即可:

all :: prog1 prog2

...

clean ::
    ...
distclean ::
    ...
:: 有其他特殊含义,可以在这里查看,但如果没有前提条件,它总是执行配方,即使目标已经存在,因此起到了虚假目标的作用。

1
你确定"::"的意思是这样吗?在"::"的文档中,根本没有提到".PHONY",而且"::"也用于非虚拟目标。 - janluke
事实上,在没有先决条件的情况下使用::时,才被视为虚假,可参见GNU make文档中上面的链接。 - Edouard Thiel
没有工作。在makefile的“target ::”行中,使用strace输出了stat(“target”)。 唯一的.PHONY:target有效。GNU Make 4.1 适用于x86_64-pc-linux-gnu - vGimly

38
最好的解释是GNU make手册本身:4.6 Phony Targets 部分.PHONY是make的一个特殊内置目标名称之一。你可能会对其他目标感兴趣,所以值得浏览这些参考资料。

当考虑.PHONY目标时,无论具有该名称的文件是否存在或其最后修改时间如何,make都将无条件地运行其配方。

你可能还会对make的标准目标allclean感兴趣。

我不同意。那个页面很混乱,除非你已经知道答案!例如,我有一个“all”目标,可以制作所有我的程序。它并没有像那个页面所建议的那样被声明为虚假目标,但它仍然有效。我仍然不知道它想告诉我什么......这是Makefile,证明了我的观点 https://github.com/m4r35n357/ODE-Playground/blob/pure_c/Makefile - m4r35n357

15

还有一个重要的“.PHONY”的技巧 - 当一个物理目标依赖于另一个虚拟目标,而这个虚拟目标又依赖于另一个物理目标时:

TARGET1 -> PHONY_FORWARDER1 -> PHONY_FORWARDER2 -> TARGET2

你可能会期望,如果你更新了TARGET2,则TARGET1应该被认为是过时的,因此需要重新构建TARGET1。实际上确实是这样的

棘手的部分是,当TARGET2不是与TARGET1过时时 - 在这种情况下,你应该期望TARGET1不需要被重新构建。

令人惊讶的是,这并不起作用,因为:虚拟目标仍然被运行了(因为虚拟目标通常都是如此),这意味着虚拟目标被认为已经更新。由于这个原因,TARGET1被认为是过时的,需要与虚拟目标一起重新构建

考虑以下示例:

all: fileall

fileall: file2 filefwd
    echo file2 file1 >fileall


file2: file2.src
    echo file2.src >file2

file1: file1.src
    echo file1.src >file1
    echo file1.src >>file1

.PHONY: filefwd
.PHONY: filefwd2

filefwd: filefwd2

filefwd2: file1
    @echo "Produced target file1"


prepare:
    echo "Some text 1" >> file1.src
    echo "Some text 2" >> file2.src

你可以试着玩一下:

  • 首先运行'make prepare'以准备源文件
  • 通过触摸特定的文件来玩弄它们,看看它们是否更新

你可以看到,文件"fileall"间接地通过一个虚构目标依赖于"file1" - 但由于这种依赖性,它总是会被重新构建。如果你将fileall中的依赖从filefwd更改为file,那么现在fileall不会每次都被重新构建,而只有在任何依赖目标对其作为文件时过时时才会被重新构建。


1
Linux内核Makefile不使用多个.PHONY目标。它们广泛使用多行变量PHONY(如PHONY += target1 target2),并且makefile的最后一行是: .PHONY:$(PHONY) - vGimly
1
那个评论是关于什么的? - Ethouris

10

假设您在运行make的目录中有一个名为“clean”的文件。现在让我们以下面的Makefile为例:

clean:
        rm lol

现在当你运行"make clean"时,你会得到以下输出: enter image description here

但是如果你在Makefile中添加".PHONY: clean"并运行"make clean",你将看到以下输出: enter image description here

发生了什么? 由于目录中存在文件,第一次make将clean视为目标。 但是添加.PHONY后,make忽略了文件(以及时间戳跟踪),并将其解释为普通的clean。

现在,这可以应用于许多情况,其中您希望make忽略给定的参数作为目标(当您在该目录中具有相同名称的文件时)。


5

我经常使用它们来告诉默认目标不要触发。

superclean: clean andsomethingelse

blah: superclean

clean:
   @echo clean

%:
   @echo catcher $@

.PHONY: superclean

没有 PHONY,make superclean 将触发 cleanandsomethingelsecatcher superclean;但使用了 PHONY,make superclean 就不会触发 catcher superclean
我们不必担心告诉 make clean 目标是 PHONY,因为它并非完全虚假。尽管它从未生成 clean 文件,但它有命令可触发,所以 make 会认为它是最终目标。
但是,superclean 目标真的是虚假的,因此 make 将尝试将其与为 superclean 目标提供依赖关系的任何其他内容堆叠在一起——这包括其他 superclean 目标和 % 目标。
请注意,我们根本不提及 andsomethingelseblah,因此它们显然进入 catcher。
输出看起来像这样:
$ make clean
clean

$ make superclean
clean
catcher andsomethingelse

$ make blah 
clean
catcher andsomethingelse
catcher blah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接