【问题标题】:ant conditional targets and 'recursion'ant 条件目标和“递归”
【发布时间】:2011-10-07 12:23:26
【问题描述】:

我对 ant 还很陌生,我看过 Bob 叔叔的“提取直到你放弃”一集。

因此,我尝试将 ant-target 定义得尽可能小,这样您就可以准确地看到目标的本质,仅此而已。更多细节,你必须参考子目标。

这种风格是好是坏是另一场争论(或者可能是一场激烈的战争)。

因此,我正在创建一个构建脚本,其伪代码如下所示:

build =
   compile
   instrument if coverage

coverage 任务也被拆分为子目标:

coverage:
   create-coverage-dirs
   call-cobertura

编辑-我想表达coverage子目标不应该运行。

但是...我很难用 ant-ese 来“干净地”表达这个。

假设我可以使用depends 属性来指示...目标间的依赖关系,我得到了这样的东西:

<target name="build" depends="compile, coverage"/>

<target name="compile"> .... </target>

<target name="coverage" depends="
       create-coverage-dirs, 
       taskdef-cobertura"
 if="build.with.coverage">

  <cobertura-instrument ...> ... </cobertura-instrument>

</target>

<target name="create-coverage-dirs">
  ...    
</target>

<target name="taskdef-cobertura">
  ...
</target>

这看起来不错!

只是似乎在执行时,coverage 任务被适当地省略了,但是当build.with.coveragefalse 时,它的子任务仍然被执行

>ant -v compile
Build sequence for target(s) `build' is 
  [compile, create-coverage-dirs, taskdef-    cobertura, coverage, build]
Complete build sequence is 
  [compile, create-coverage-dirs, taskdef-cobertura, coverage,    build, ]

我可以在每个覆盖子任务中添加一个if 属性,但这对我来说似乎并不干净。

那么问题来了:

  1. 我的ant-ese是一种可怕的方言吗?我是在“把蚂蚁变成制造”吗?
  2. 应该以这种方式使用if,还是有if-and-recurse 类型的属性?

【问题讨论】:

    标签: coding-style ant if-statement dependencies


    【解决方案1】:

    跟我重复一遍:Ant 不是编程语言。事实上,在黑板上写下 100 遍。

    Ant 不是一种编程语言,所以不要这样想。它是一个构建依赖矩阵。

    程序员很难接受这个想法。他们想告诉 Ant 每一步以及何时应该完成。他们想要循环,if 语句。他们会求助于有一个 build.sh 脚本来调用 Ant 中的各种目标,因为您不能轻松地对 Ant 进行编程。

    在 Ant 中,您可以指定离散任务,哪些任务依赖于其他任务,并让 Ant 处理事情的执行地点和时间。

    我的意思是,您通常不会将任务拆分为子任务,然后尝试对它们调用 &lt;ant&gt;&lt;subant&gt;

    有离散的任务,然后让每个任务知道他们依赖的其他任务。还要记住,Ant 中没有真正的顺序。当您列出 depends= 任务时,无法保证它们将按什么顺序执行。


    标准 Ant 样式(这意味着我这样做的方式(又名正确的方式),而不是我同事的方式(又名错误的方式)),通常声明在属性文件的顶部定义任务和不在任何目标中。这是我如何构建build.xml 的基本大纲:

    <project name=...>
    
          <!-- Build Properties File -->
          <property name="build.properties.file"
               value="${basedir}/build.properties"/>
          <property file="${build.properties.file"/>
    
          <!-- Base Java Properties -->
          <property name="..." value="..."/>
    
          <taskdef/>
          <taskdef/>
    
          <!-- Javac properties -->
          <property name="javac..."  value="..."/>
    
          <task/>
          <task/>
     </project>
    

    这创建了一个有趣的层次结构。如果您有一个名为build.properties 的文件,它将覆盖build.xml 脚本中定义的属性。例如,您有:

    <property name="copy.verbose"   value="false"/>
    
    <copy todir="${target}"
       verbose="${copy.verbose}">
       <fileset dir="${source}"/>
    </copy>
    

    您只需在build.properties 文件中设置copy.verbose = true 即可打开详细副本。而且,您可以通过仅在命令行上指定它来指定不同的构建属性文件:

    $ ant -Dbuild.properties.file="my.build.properties"
    

    (是的,是的,我知道-propertyant 的命令行参数)

    我通常将build.xml 中的各种值设置为假定的默认值,但任何人都可以通过创建build.properties 文件来更改它们。而且,由于所有基本属性都位于开头,因此很容易找到。

    任务也在这个非目标空间中定义。这样,我可以很容易地找到定义,因为它们在每个build.xml 中都位于相同的位置,并且我知道我可以使用任务而不必担心任务定义目标是否已命中。

    现在,回答你的问题:

    定义你的任务(不要有一个 tar 定义任务,否则你会把自己逼疯的)。然后,定义每个任务的依赖关系。开发人员可以选择他们想要达到的目标。例如:

    <project>
       <description>
          yadda, yadda, yadda
       </description>
    
       <taskdef name="cobertura"/>
    
       <target name="compile"
            description="Compile the code"/>
    
        <!-- Do you have to compile code before you run Cobertura?--> 
        <target name="coverage"
           description="Calculate test coverage"
           depends="compile">
    
           <mkdir dir="${coverage.dir}"/>
           <cobertura-instrument/>
        </target>
    <project>
    

    如果您想编译您的代码,但不运行任何测试,您可以使用compile 目标执行ant。如果要运行测试,请使用 coverage 目标执行 ant。不需要depends= 参数。

    还要注意description= 参数和&lt;description&gt; 任务。那是因为如果你这样做:

    $ ant -p
    

    Ant 将显示&lt;description&gt; 任务中的内容、带有description 参数的所有目标以及该描述。这样,开发人员就知道哪些目标用于哪些任务。

    顺便说一句,我还建议以正确的方式做事(也就是按照我的方式做事)并以Maven lifecycle goals 命名您的目标。为什么?因为这是标准化目标名称的好方法。开发人员知道clean 将删除所有构建的工件,compile 将运行&lt;javac&gt; 任务,test 将运行junit 测试。因此,您应该使用Cobertura plugin 中的目标:cobertura


    编辑

    我的问题是:我认为“覆盖”与“优化”和“调试”相关,即构建风格。这就是我的困难所在:对于 Java,覆盖会在编译步骤中产生一个额外的中间目标。

    我正在查看 Corburta 页面,&lt;javac&gt; 任务(它是 compile 目标的一部分。

    相反,您在已构建的 .class 文件上运行 Corburtura,然后运行您的 &lt;junit&gt; 任务。最大的变化在于您的 &lt;junit&gt; 任务现在必须包含对您的 Corburtura jar 和您的检测类的引用。

    我想你可以有一个corburturatarget 或者你想怎么称呼它。此目标运行检测的 JUnit 测试。这是您希望开发人员达到的目标,并且应该包含运行插桩测试的描述。

    当然,如果不先检测它们,就无法运行检测的 Junit 测试。因此,您的corburtura 目标将依赖于另一个instrument.tests 目标。这个目标是内部的。运行您的build.xml 的人通常不会在不运行这些测试的情况下说“仪器测试”。因此,这个目标没有描述。

    当然,instrument.tests 目标依赖于要检测的.class 文件,因此它将依赖于运行&lt;javac&gt; 任务的compile 目标:

    <target name="instrument.classes"
       depends="compile">
       <coburtura-instrument/>
    </target>
    
    <target name="corburtura"
       depends="instrument.classes"
       description="Runs the JUnit tests instrumented with Corburtura">
       <junit/>
    </target>
    

    唯一的问题是您指定了两次&lt;junit&gt; 目标:一次是在检测时,一次是在正常测试时。这可能是一个小问题。如果您更新 JUnit 测试的运行方式,则必须在两个地方进行。

    如果你想解决这个问题,你可以使用&lt;macrodef&gt;来定义一个JUnit测试运行宏。我使用了Corbertura 页面上的内容来帮助绘制大纲。完全未经测试,可能充满语法错误:

    <target name="instrument.tests"
        depends="compile">
        <corburtura-instrument/>
    </target>
    
    <target name="corburtura"
        depends="instrument.tests"
        description="Instrument and run the JUnit tests">
    
        <run.junit.test fork.flag="true">
            <systemproperty.addition>
                <sysproperty key="net.sourceforge.corbertura.datafile"
                    file="${basedir}/cobertura.ser" />
            </systemproperty.addition>
            <pre.classpath>
                <classpath location="${instrumented.dir}" />
            </pre.classpath>
            <post.classpath>
                <classpath refid="cobertura_classpath" />
            </post.classpath>
        </run.junit.test>
    </target>
    
    <target name="test"
        description="Runs the Junit tests without any instrumentation">
        <run.junit.test/>
    </target>
    
    <macrodef name="run.junit.test">
        <attribute name="fork.flag" default="false"/>
        <element name="sysproperty.addition" optional="yes"/>
        <element name="pre.classpath" optional="yes"/>
        <element name="post.classpath" optional="yes"/>
        <sequential>
            <junit fork="@{fork.flag}" dir="${basedir}" failureProperty="test.failed">
    
                <systemproperty.addtion/>
                <pre.classpath/>
                <classpath location="${classes.dir}" />
                <post.classpath/>
    
                <formatter type="xml" />
                <test name="${testcase}" todir="${reports.xml.dir}" if="testcase" />
                <batchtest todir="${reports.xml.dir}" unless="testcase">
                    <fileset dir="${src.dir}">
                        <include name="**/*Test.java" />
                    </fileset>
                </batchtest>
            </junit>
        </sequential>
    </macrodef>
    

    【讨论】:

    • 哇,感谢您提供如此详尽的答案。我完全同意depends= i.s.o。 antcall;我的问题是:我认为“覆盖”与“优化”和“调试”有关,即构建风格。这就是我的困难所在:对于 Java,覆盖会导致编译步骤中的一个额外的中间目标
    • 就像例如在进行发布构建时,我对未优化的代码不感兴趣,我不需要任何 javac 输出。
    • 顺便说一句-我将构建样式称为“coverage”iso“cobertura”,因为我想指定 它的作用 iso 谁做它 .这对我来说是正确的方式,因为我也在使用相同的语义进行 C++ 构建。
    • 我已经有一段时间没有使用 Corbertura,但我不记得 &lt;javac&gt; 任务中的任何变化。相反,您获取在&lt;javac&gt; 中生成的.class 文件,并使用Cobertura instrument 它们。真正的变化是在&lt;junit&gt; 任务中,当您必须派生 Junit、将检测类合并到类路径中并包含所有特殊的 Corbertura jar 时。您可以简单地运行 &lt;junit&gt; 作为 Corbertura 目标的一部分。您可以复制它,也可以使用&lt;macrodef&gt; 定义&lt;junit&gt;,因此您可以使用参数来指定(或不指定)Cobertura 更改。见上文。
    【解决方案2】:

    在这种情况下,我根本不会使用属性,而是完全依赖 depends(这对我来说似乎更自然地完成这项任务):

    <target name="build" depends="compile, coverage"/>
    
    <target name="compile"> ...
    
    <target name="coverage" 
            depends="compile, instrument,
                     create-coverage-dirs, taskdef-cobertura"> ...
    

    【讨论】:

    • 这也是我的感觉。只有coverage 也依赖于compile,不是吗?
    【解决方案3】:

    if attribute 测试属性是否存在,而不是测试它的真假。如果您不想运行覆盖目标,则不要定义属性build.with.coverage

    从 Ant 1.8.0 开始,您可以使用属性扩展来将属性恢复为布尔值:

    <target name="coverage" depends="
               create-coverage-dirs, 
               taskdef-cobertura"
            if="${build.with.coverage}">
    

    【讨论】:

    • 感谢您的信息;即使没有定义build.with.coverage,唯一被排除的目标是“主要”coverage 目标。我想表示它的子目标也不应该运行。
    • 您可以从依赖列表中删除子目标,然后通过覆盖任务内部的antcall 调用它们。
    • Antcall 与depends 不同: 不会覆盖现有引用,即使您将inheritRefs 设置为true。由于被调用的构建文件与调用的构建文件相同,这意味着它根本不会覆盖任何通过 id 属性设置的引用。子项目唯一可以继承的引用是由嵌套的 元素定义的引用或由任务直接定义的引用(不使用 id 属性)。见ant.apache.org/manual/Tasks/antcall.html
    • 是的,我可以,但这确实意味着我决定了执行的顺序。我试图避免这种情况,但也许我 正在将 ant 变成 make...
    • @xtofl:执行顺序可能与depends中的声明顺序不同,但声明顺序在执行顺序的计算中肯定很重要:depends = "a, b"与@987654330不相似@
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-28
    • 2016-03-31
    相关资源
    最近更新 更多