makefile中.PHONY的目的是什么?

What is the purpose of .PHONY in a makefile?

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

有人能简单地给我解释一下吗?


默认情况下,makefile目标是"文件目标"——它们用于从其他文件构建文件。make假定其目标是一个文件,这使得编写makefiles相对容易:

1
2
foo: bar
  create_one_from_the_other foo bar

但是,有时您希望makefile运行不代表文件系统中物理文件的命令。这方面的好例子是"干净"和"全部"的共同目标。很可能情况并非如此,但您的主目录中可能有一个名为clean的文件。在这种情况下,make将被混淆,因为默认情况下,clean目标将与该文件关联,并且make将仅在该文件在依赖项方面似乎不是最新的时运行它。

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

1
2
3
.PHONY: clean
clean:
  rm -rf *.o

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

在make方面,虚假目标只是一个总是过时的目标,因此每当您询问make 时,它将独立于文件系统的状态运行。一些常见的假目标是:allinstallcleandistcleanTAGSinfocheck


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

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

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


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

例子

在目录"test"中,存在以下文件:

1
2
prerit@vvdn105:~/test$ ls
hello  hello.c  makefile

在makefile中,规则定义如下:

1
2
hello:hello.c
    cc hello.c -o hello

现在假设文件"hello"是包含一些数据的文本文件,这些数据是在"hello.c"文件之后创建的。所以"hello"的修改(或创建)时间戳将比"hello.c"更新。因此,当我们从命令行调用"make hello"时,它将打印为:

1
make: `hello' is up to date.

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

1
cc hello.c -o hello

文件"hello"(文本文件)将被一个新的二进制文件"hello"(上述编译命令的结果)覆盖。

如果在makefile中使用.phony,如下所示:

1
2
3
4
.PHONY:hello

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

然后调用"make hello",它将忽略pwd中名为"hello"的文件,并每次执行该命令。

现在假设makefile中没有目标的依赖项:

1
2
hello:
    cc hello.c -o hello

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

1
make: `hello' is up to date.


1
.PHONY: install
  • 表示"安装"一词不代表此文件中的文件名生成文件;
  • 表示makefile与名为"install"的文件无关在同一个目录中。

它是不是文件名的生成目标。


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

.PHONY是make的特殊内置目标名称之一。还有其他你可能感兴趣的目标,所以值得浏览一下这些参考资料。

When it is time to consider a .PHONY target, make will run its recipe
unconditionally, regardless of whether a file with that name exists or
what its last-modification time is.

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


还有一个重要的棘手的处理方法就是"假"—当一个物理目标依赖于另一个物理目标的假目标时:

目标1->Phony_转发器1->Phony_转发器2->Target2

如果更新了target2,那么应该认为target1相对于target1过时,所以应该重新构建target1。它确实是这样工作的。

棘手的部分是,当target2对target1不过时-在这种情况下,你应该期待target1不应该被重建。

这令人惊讶地不起作用,因为:不管怎样,假目标都是运行的(就像假目标通常那样),这意味着假目标被认为是更新的。因为这个目标1被认为是对虚假目标的过时。

考虑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

你可以玩这个:

  • 先做"准备"准备"源文件"
  • 通过触摸特定文件以查看更新的文件,来玩转它。

您可以看到,fileall通过虚假目标间接地依赖于file1,但由于这种依赖性,它总是得到重建。如果您将fileall中的依赖项从filefwd更改为file,那么现在fileall不会每次都进行重建,而是仅当任何依赖目标作为文件过时时才进行重建。


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

1
2
3
4
5
6
7
8
9
10
11
superclean: clean andsomethingelse

blah: superclean

clean:
   @echo clean

%:
   @echo catcher $@

.PHONY: superclean

没有假身份,make superclean会解雇cleanandsomethingelsecatcher superclean;但是有假身份,make superclean不会解雇catcher superclean

我们不必担心告诉你让clean目标是假的,因为它不是完全假的。虽然它从未生成干净的文件,但它有要激发的命令,因此make会认为它是最终的目标。

然而,superclean目标确实是假的,因此make将尝试将其与为superclean目标提供DEP的任何其他目标进行叠加,这包括其他superclean目标和%目标。

请注意,我们根本不说关于andsomethingelseblah的任何事情,因此它们显然是指向捕手的。

输出如下所示:

1
2
3
4
5
6
7
8
9
10
11
$ make clean
clean

$ make superclean
clean
catcher andsomethingelse

$ make blah
clean
catcher andsomethingelse
catcher blah