Makefile中. PHONY的目的是什么?

.PHONY在Makefile中是什么意思?我已经经历了这个,但它太复杂了。

谁能简单地给我解释一下?

593828 次浏览

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

foo: barcreate_one_from_the_other foo bar

但是,有时您希望您的Makefile运行不代表文件系统中的物理文件的命令。这方面的好例子是常见的目标“干净”和“全部”。情况可能并非如此,但您的主目录中可能有一个名为clean的文件。在这种情况下,Make会感到困惑,因为默认情况下clean目标将与此文件相关联,而Make只会在文件在其依赖项方面似乎不是最新的情况下运行它。

这些特殊目标称为虚假,您可以明确告诉Make它们与文件无关,例如:

.PHONY: cleanclean:rm -rf *.o

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

就Make而言,虚假目标只是一个总是过时的目标,因此每当您询问make <phony_target>时,它都会运行,与文件系统的状态无关。一些常见的make目标通常是虚假的:allinstallcleandistcleanTAGSinfocheck

它是一个构建目标,而不是文件名。

假设您有install目标,这在makefile中很常见。如果您使用.PHONY执行没有,并且名为install的文件与Makefile存在于同一目录中,那么make install将执行什么都没有。这是因为Make将规则解释为“执行某某配方以创建名为install的文件”。由于文件已经存在,并且其依赖项没有更改,因此不会执行任何操作。

但是,如果您创建install目标PHONY,它会告诉make工具目标是虚构的,并且make不应期望它创建实际文件。因此,它不会检查install文件是否存在,这意味着:a)如果文件确实存在,它的行为不会改变,b)不会调用额外的stat()

通常,Makefile中所有不生成与目标名称同名的输出文件的目标都应该是PHONY。这通常包括allinstallcleandistclean等。

.PHONY: install
  • 表示单词“安装”不代表此文件中的文件名创建文件;
  • 表示Makefile与名为“install”的文件无关在同一个目录中。

:make工具读取makefile并检查规则中':'符号两侧文件的修改时间戳。

示例

在目录“test”中存在以下文件:

prerit@vvdn105:~/test$ lshello  hello.c  makefile

在makefile中,规则定义如下:

hello:hello.ccc 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.ccc hello.c -o hello

然后调用make hello,它将忽略pwd test中存在的任何文件并每次执行该命令。

现在假设,“hello”目标没有声明依赖项:

hello:cc hello.c -o hello

并且'hello'文件已经存在于pwd'test'中,那么'make hello'将始终显示为:

make: `hello' is up to date.

最好的解释是GNU make手册本身:4.6虚假目标部分

.PHONY是make的特殊的内置目标名称之一。您可能还对其他目标感兴趣,因此值得浏览这些参考文献。

当需要考虑. PHONY目标时,make将运行它的配方无条件地,无论是否存在具有该名称的文件或最后修改时间是什么?

您可能还对make的标准目标感兴趣,例如allclean

还有一个重要的棘手处理“. PHONY”-当一个物理目标依赖于另一个物理目标的虚假目标时:

目标1->PHONY_FORWARDER1->PHONY_FORWARDER2->目标2

您只是期望如果您更新了TARGET2,那么TARGET1应该被认为是针对TARGET1的陈旧,因此TARGET1应该被重建。

棘手的部分是当TARGET2不是对TARGET1过时时-在这种情况下,您应该期望TARGET1不应该重建。

这令人惊讶地不起作用,因为:无论如何都运行了虚假目标(就像虚假目标通常做的那样),这意味着假目标被认为是最新的。因为那个TARGET1被认为是针对虚假目标的陈旧

考虑:

all: fileall
fileall: file2 filefwdecho file2 file1 >fileall

file2: file2.srcecho file2.src >file2
file1: file1.srcecho file1.src >file1echo file1.src >>file1
.PHONY: filefwd.PHONY: filefwd2
filefwd: filefwd2
filefwd2: file1@echo "Produced target file1"

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

你可以玩这个:

  • 首先做'make准备'来准备“源文件”
  • 通过触摸特定文件来查看它们的更新

你可以看到fileall通过一个虚假的目标间接依赖于file1——但由于这种依赖关系,它总是会被重建。如果你将fileall中的依赖项从filefwd更改为file,现在fileall不会每次都被重建,但只有当任何依赖目标作为文件对它过时时才会被重建。

我经常用它们来告诉默认目标不要开火。

superclean: clean andsomethingelse
blah: superclean
clean:@echo clean
%:@echo catcher $@
.PHONY: superclean

如果没有PHONY,make superclean会触发cleanandsomethingelsecatcher superclean;但是有了PHONY,make superclean不会触发catcher superclean

我们不必担心告诉makeclean目标是PHONY,因为它不完全是假的。虽然它永远不会生成干净的文件,但它有触发的命令,所以make会认为它是最终目标。

但是,superclean目标确实是假的,因此make将尝试将其与为superclean目标提供deps的任何其他内容堆叠起来-这包括其他superclean目标和%目标。

请注意,我们根本没有说任何关于andsomethingelseblah的内容,所以它们显然是指向捕手的。

输出看起来像这样:

$ make cleanclean
$ make supercleancleancatcher andsomethingelse
$ make blahcleancatcher andsomethingelsecatcher blah

特殊目标.PHONY:允许声明虚假目标,因此make不会将它们检查为实际文件名:即使此类文件仍然存在,它也会一直工作。

你可以在你的Makefile中放几个.PHONY:

.PHONY: all
all : prog1 prog2
...
.PHONY: clean distclean
clean :...distclean :...

还有另一种方法可以声明虚假目标:简单地将::放在没有先决条件的情况下:

all :: prog1 prog2
...
clean ::...distclean ::...

::还有其他特殊含义,参见这里,但如果没有先决条件,它总是执行配方,即使目标已经存在,因此充当虚假目标。