【问题标题】:Is there a way to guarantee that an ant dependency is run only once?有没有办法保证一个 ant 依赖项只运行一次?
【发布时间】:2018-10-13 04:19:19
【问题描述】:

我的问题类似于avoiding-re-building-prerequisites-in-ant,除了我的需要不涉及创建的对象,而是调用的进程,因此那里讨论的解决方案对我不起作用。至少我是这么认为的——但我是 ant 新手。

我的情况是我正在编写一组 ant 目标,并且无论调用哪个目标,我都需要只执行一次 let-call-it setup 目标。这是一个大大简化的示例:


    <?xml version="1.0"?>
    <project name="Ant_Test" basedir=".">
      <target name="setup">
        <echo message="In setup" />
      </target>
      <target name="do.several" depends="setup">
        <echo message="In do.several, calling do.first" />
        <antcall target="do.first" />
        <echo message="In do.several, calling do.second" />
        <antcall target="do.second" />
      </target>
      <target name="do.first" depends="setup">
        <echo message="In do.first" />
      </target>
      <target name="do.second" depends="setup">
        <echo message="In do.second" />
      </target>
    </project>

我需要 setup 只被调用一次,无论是 do.severaldo.first 还是 do。第二个 被调用。根据我上面的天真尝试,调用 do.several 会导致对 setup 的三个调用。

我想过设置一个属性(我们称之为 setup.has.been.invoked),并使用它在每个目标中有条件地调用 setup,但似乎属性设置仅限于它所做的范围,所以如果在 setup 中,我将 setup.has.been.invoked 设置为 true,则该值仅存在在设置中。

我错过了什么?有没有我跳过的教程或在线文档的一部分?任何指针或提示?

【问题讨论】:

    标签: ant


    【解决方案1】:

    您应该删除antcalls 并添加do.firstdo.second 作为do.several 的依赖项:

    <target name="do.several" depends="setup, do.first, do.second">
    </target>
    

    这将确保 setup 只被调用一次:

    setup:
         [echo] In setup
    
    do.first:
         [echo] In do.first
    
    do.second:
         [echo] In do.second
    
    do.several:
    
    BUILD SUCCESSFUL
    Total time: 0 seconds
    

    文档说明了为什么 setup 中设置的属性不适用于 antcall:

    被调用的目标在一个新项目中运行;请注意,这意味着被调用目标设置的属性、引用等不会持久化回调用项目。

    【讨论】:

    • +!摆脱&lt;antcall&gt;&lt;antcall&gt; 让 David 伤心... 99% 的时间,&lt;antcall&gt; 不仅没有必要,而且还会破坏构建系统的效率。开发人员喜欢使用&lt;antcall&gt;,因为他们习惯于控制执行顺序。一个 Ant build.xml 文件,我们至少调用了每个依赖项 14 次。 Ant 是一种矩阵依赖语言。您应该只说明每个任务的依赖关系,让 Ant 确定顺序。
    【解决方案2】:

    我只是想添加另一种可能的方式来做到这一点。

    <target name="setup" unless="setup.already.executed">
        <echo message="In setup" />
        <property name="setup.already.executed" value="x" />
    </target>
    

    这样你只运行一次,然后立即设置它已经执行过一次的标志。此外,它不会破坏代码的“依赖”部分,因为它仅在可能/必要时运行目标,但不会破坏目标依赖目标的执行。

    这也是脚本中最少的更改。

    编辑: “不破坏依赖部分”的解释:

    如果调用 'ant do.first do.second',它会导致 setup 被调用两次,即使所有目标都使用 setup 作为依赖项。如果安装程序正在执行诸如克隆存储库或其他耗时操作之类的操作,那将是一个问题。这种方法适用于两种情况 - 即“ant do.several”或“ant do.first do.second”。

    【讨论】:

      【解决方案3】:

      您已经收到的答案的替代方法是创建一个自定义任务容器,以确保它不会重复某个操作。我在my personal Antlib 中有这样一个自定义任务,但是那里有一大堆您可能不想要的其他垃圾,所以也许您可以复制the source 并将其添加到您自己的项目中。它看起来像这样:

      import org.apache.tools.ant.Task;
      import org.apache.tools.ant.TaskContainer;
      import org.apache.tools.ant.BuildException;
      import java.util.List;
      import java.util.LinkedList;
      
      /**
       * Task container to ensure that some set of actions is only performed
       * once per build.  On completion of the actions a property is set that
       * prevents subsequent executions.
       */
      public class Once extends Task implements TaskContainer
      {
          private final List<Task> tasks = new LinkedList<Task>();
      
          private String property;
      
          /**
           * The name of the property to consult in order to determine whether
           * the nested tasks should be performed.  This is a required attribute.
           */
          public void setProperty(String property)
          {
              this.property = property;
          }
      
          public void addTask(Task task)
          {
              tasks.add(task);
          }     
      
          @Override
          public void execute() throws BuildException
          {
              if (property == null || property.length() == 0)
              {
                  throw new BuildException("Property name must be specified.");
              }
              // If no value is specified, the tasks are performed if the property
              // is set to any value.  If a value is specified, the tasks are only
              // performed if the property matches that value.
              if (getProject().getProperty(property) == null)
              {
                  for (Task task : tasks)
                  {
                      task.perform();
                  }
                  getProject().setProperty(property, "done");
              }
          }
      }
      

      【讨论】:

      • 谢谢,丹。我不知道自定义 TaskContainer 是可能的。很明显,我还有更多的阅读要做。
      • 干得好,丹!迫不及待地想用 Ivy、Hudson 尝试不常见的东西,尤其是使用 maven-ant 任务 - 使构建、部署和测试变得更加容易。谢谢。
      【解决方案4】:

      目标可以有一个“除非”元素,如果设置了“除非”引用的属性,该元素将跳过目标。

      【讨论】:

        【解决方案5】:

        查看 Ant 手册中 includeimport 之间的区别。也可以使用宏定义

        我稍微修改了您的示例,您需要几个文件: build.xml、common.xml 和 macrodef_project_setup.xml

        build.xml

        <?xml version="1.0"?>
        <project name="Ant_Test" basedir="." default="init">
        
        <import file="common.xml"/>
        
        <!-- This target overridden by the one in common.xml -->
        <target name="common.init"/>
        
        <target name="setup" depends="init"/>
        
        <target name="do.several" depends="common.setup">
            <echo message="In do.several, calling do.first" />
            <antcall target="do.first" />
            <echo message="In do.several, calling do.second" />
            <antcall target="do.second" />
        </target>
        
        <target name="do.first" depends="common.setup">
            <echo message="In do.first" />
        </target>
        <target name="do.second" depends="common.setup">
            <echo message="In do.second" />
        </target>
        
        </project>
        

        common.xml

        <?xml version="1.0"?>
        <project name="common">
        
        <target name="init">
            <project_setup option="Stack.Over.Flow"/>
        </target>
        
        <target name="setup">
        </target>
        
        <import file="macrodef_project_setup.xml"/>
        
        </project>
        

        宏定义

        <?xml version="1.0"?>
        <project name="project_setup" basedir=".">
        
        <macrodef name="project_setup">
                <attribute name="option" default=""/>
                <sequential>
        
                    <!-- some process -->
                    <echo>THIS IS MY SETUP OPTION: @{option}</echo>
        
                </sequential>
        </macrodef>
        
        </project>
        

        输出:

        ant -p
        Buildfile: build.xml
        Main targets:
        
        Other targets:
        
         common.setup
         do.first
         do.second
         do.several
         init
         setup
        Default target: init
        

        默认目标现在是 init。

        ant
        Buildfile: build.xml
        
        Ant_Test.init:
             [echo] In setup initialization
             [echo] THIS IS MY SETUP OPTION: Stack.Over.Flow
        

        但你仍然可以使用 ant setup。

        ant setup
        Buildfile: build.xml
        
        Ant_Test.init:
             [echo] In setup initialization 
             [echo] THIS IS MY SETUP OPTION: Stack.Over.Flow
        

        用 do.several 运行它。

        ant do.several
        Buildfile: build.xml
        
        Ant_Test.init:
             [echo] In setup initialization 
             [echo] THIS IS MY SETUP OPTION: Stack.Over.Flow
        
        Ant_Test.do.several:
             [echo] In do.several, calling do.first
        
        Ant_Test.do.first:
             [echo] In do.first
        
             [echo] In do.several, calling do.second
        
        Ant_Test.do.second:
             [echo] In do.second
        

        【讨论】:

        • 谢谢,院长。这是非常令人印象深刻的。我确实会更详细地了解包含、导入和宏定义。谢谢。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-11-23
        • 1970-01-01
        • 2021-02-15
        • 1970-01-01
        • 2021-12-23
        • 2020-03-21
        • 2011-03-18
        相关资源
        最近更新 更多