【问题标题】:What is the purpose of .PHONY in a Makefile?Makefile 中 .PHONY 的用途是什么?
【发布时间】:2011-01-09 21:01:57
【问题描述】:

.PHONY 在 Makefile 中是什么意思?我已经通过this,但是太复杂了。

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

【问题讨论】:

    标签: makefile phony-target


    【解决方案1】:

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

    foo: bar
      create_one_from_the_other foo bar
    

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

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

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

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

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

    【讨论】:

    • @eSKay: '为什么叫'假'?' ——因为它不是一个真正的目标。也就是说,目标名称不是由该目标的命令生成的文件。
    • @Lazer:我不知道你是否以英语为母语。我不是。假这个词并不意味着它听起来的样子。 en.wiktionary.org/wiki/phony 说:欺诈;伪造的;具有误导性的外观。
    • 这个答案并不完全完整 - 尽管它可能在链接的教程中得到解决。 .PHONY 强制构建 Makefile 中的标签/文件,如果它是您的目标的拓扑排序的一部分。也就是说,如果您有一个设置为假的“cleanup:”标签,并且您的安装标签是以 cleanup 作为先决条件定义的 - 即“install:cleanup”,那么 cleanup 将总是当 Makefile 尝试构建“安装”时运行。这对于您总是希望采取的步骤(无论它们是否成功)很有用 - 它会忽略时间戳并强制执行。
    • 请注意,只要您没有与任务同名的文件,您就不需要使用 .PHONY。无论如何,任务总是会被执行,而且 Makefile 将更具可读性。
    • “虚假目标只是一个总是过时的目标”——很好的解释!
    【解决方案2】:

    这是一个不是文件名的构建目标。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • @PineappleUndertheSea 接受的答案已经从最初的无价值水平显着提高,现在和这个一样好。我必须查看它的修订历史才能理解您的评论。
      • 这似乎毫无意义,因为我的代码库中永远不会有名为“安装”之类的文件。大多数文件都会有文件扩展名,而没有文件扩展名的文件通常都是大写的,比如“README”。再说一次,如果你有一个名为 'install' 而不是 'install.sh' 的 bash 脚本,那你会很糟糕。
      • @JasonTu 这不一定是真的。 Bash 脚本约定要求您省略“程序”的 .sh.bash 扩展名,这些程序运行起来就像它们具有主函数一样,并保留为您包含的库添加扩展名 (source mylib.sh)。事实上,我之所以遇到这个 SO 问题,是因为我在与 Makefile 相同的目录中有一个名为 install 的脚本
      • @Kyle 是的,我不确定我过去的自我是什么意思。这些天我一直在使用.PHONY...
      • @JasonTu 这里的解决方案很简单:建造一台时间机器并“替换”过去的自己。我建议随身携带一把铲子,这样没人知道你是.PHONY 版本。
      【解决方案4】:
      .PHONY: install
      
      • 表示“安装”一词在此不代表文件名 生成文件;
      • 表示 Makefile 与名为“install”的文件无关 在同一目录中。

      【讨论】:

        【解决方案5】:

        注意: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
        

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

        make: `hello' is up to date.
        

        【讨论】:

        • 这不仅使我运行的命令变得有意义,而且最终使make 整体变得有意义,一切都与文件有关!谢谢你的回答。
        • 下面是一个简单的规则:```目标:依赖项...命令...```参考:gnu.org/software/make
        【解决方案6】:

        最好的解释是 GNU make 手册本身:4.6 Phony Targets section

        .PHONY 是 make 的 Special Built-in Target Names 之一。还有其他您可能感兴趣的目标,因此值得浏览这些参考资料。

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

        您可能还对品牌的Standard Targets 感兴趣,例如allclean

        【讨论】:

          【解决方案7】:

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

          TARGET1 -> PHONY_FORWARDER1 -> PHONY_FORWARDER2 -> TARGET2

          您只是希望,如果您更新了 TARGET2,那么 TARGET1 相对于 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 不会每次都重新构建,但只有当任何依赖目标作为文件过时时才会重新构建。

          【讨论】:

            【解决方案8】:

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

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

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

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

            然而,superclean 目标确实是假的,所以 make 会尝试将它与任何其他为 superclean 目标提供 deps 的东西叠加在一起——这包括其他 superclean 目标和 % 目标。

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

            输出看起来像这样:

            $ make clean
            clean
            
            $ make superclean
            clean
            catcher andsomethingelse
            
            $ make blah 
            clean
            catcher andsomethingelse
            catcher blah
            

            【讨论】:

              【解决方案9】:

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

              您可以在您的Makefile 中添加多个.PHONY:

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

              还有另一种声明虚假目标的方法:只需输入 :: 即可:

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

              :: 有其他特殊含义,请参阅here,但在没有先决条件的情况下,它始终执行配方,即使目标已经存在,因此充当虚假目标。

              【讨论】:

              • 你确定这就是“::”的意思吗?在doc of "::" 中,根本没有提到 .PHONY,“::”也用于非虚假目标。
              • 事实上,只有在没有先决条件的情况下使用:: 时才会隐含 phony,请参阅上面 gnu make 文档中的链接。