4

我真的对为 Ant 插件定义 taskdef 的正确(最现代、最佳实践等)方式感到困惑。下面是我从我们内部的一个 Java 应用程序中拼凑出来的一段代码build.xml。请注意,以下所有 Ant 目标都 100% 完美运行(即使我已经将它们剪切并粘贴在一起,并且为了简洁起见删除了它们的大部分内容):

<project name="MyApp" default="package" basedir="." xmlns:jacoco="antlib:org.jacoco.ant">
    <path id="cobertura.path">
        <!-- ... -->
    </path>

    <taskdef name="jacoco-coverage" classname="org.jacoco.ant.CoverageTask"/>
    <taskdef name="jacoco-report" classname="org.jacoco.ant.ReportTask"/>
    <taskdef classpathref="cobertura.path" resource="tasks.properties" />
    <taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask"/>

    <target name="run-coco" depends="doOtherStuff">
        <jacoco:coverage>
            <!-- ... -->
        </jacoco:covergae>

        <jacoco-report>
            <!-- ... -->
        </jacoco-report>        
    </target>

    <target name="findbugs">
        <antcall target="compile" />
        <findbugs home="${findbugs.home}" output="xml:withMessages" outputFile="findbugs.xml">
            <!-- ... -->
        </findbugs>
    </target>
</project>
  1. 为什么我需要为 JaCoCo 而不是为 Findbugs 定义 XML 命名空间?
  2. 什么是反库?有时我在 XML 命名空间定义中看到它,有时它不存在。
  3. 尽管taskdefjacoco-coveragejavacoco-reporttaskdef 在其名称中都使用连字符(“-”),但相应的任务被分别调用jacoco:coverage……jacoco-report为什么用冒号(“:”)表示覆盖,以及它是如何工作的(确实如此,相信我! )?

提前致谢!

4

1 回答 1

10

让我们一步一步来回答这个问题:

步骤#1:定义你的任务

您可以通过以下两种方式之一定义任务:

  1. 你可以指向一个类文件,然后给这个类文件定义的任务命名
  2. 您可以指向jar 中包含任务名称和这些任务名称使用的类文件的资源文件。

在你的 JaCoCo 案例中,你做了第一个:

<taskdef name="jacoco-coverage" 
    classname="org.jacoco.ant.CoverageTask"/>
<taskdef name="jacoco-report"
    classname="org.jacoco.ant.ReportTask"/>

但是,您也可以采用第二种方式:

<taskdef resource="org/jacoco/ant/antlib.xml"/>

如果您打开 jacoco jar 文件并在该目录中向下钻取,您将看到一个名为antlib.xml. 如果您查看该文件,您将看到以下内容:

<antlib>
    <taskdef name="coverage"  classname="org.jacoco.ant.CoverageTask"/>
    <taskdef name="agent"     classname="org.jacoco.ant.AgentTask"/>
    <taskdef name="report"    classname="org.jacoco.ant.ReportTask"/>
    <taskdef name="merge"     classname="org.jacoco.ant.MergeTask"/>
    <taskdef name="dump"      classname="org.jacoco.ant.DumpTask"/>
</antlib>

因此,如果 jar 中有一个资源文件,您可以使用单个<taskdef>. 请注意,您所调用jacoco-coverage的只是coverage在这里调用,而您所调用jacoco-report的则report在此处调用。

在您用作<jacoco-coverage/>Ant 任务的地方,我会使用<coverage/>.

步骤#2:JAR 文件在哪里?

在上面,我将任务定义为:

<taskdef resource="org/jacoco/ant/antlib.xml">

某处,Ant 必须在其类路径中找到该路径,但在哪里呢?如果您将 JaCoCo jar 放入您的$ANT_HOME/lib文件夹,它将自动包含在您的类路径中。它使定义任务变得非常容易。不幸的是,这也意味着如果其他人想要运行您的 Ant 脚本,他们必须下载该 JaCoCo jar 并将其放入他们的$ANT_HOME/lib文件夹中。不是很便携。

幸运的是,您可以指定 JaCoCo jar 的位置。由于您的项目通常受版本控制,因此放置它的最佳位置是在项目所在目录的下方build.xml。当有人签出您的项目时build.xml,他们也会得到 JaCoCo.jar。

我的偏好是创建一个名为的目录antlib,然后在该antlib目录中为每组任务创建一个子目录。在这种情况下,我会有一个名为antlib/jacoco.

一旦我的 jacoco.jar 文件被安全安置。在那个目录中,我可以添加一个<classpath>子实体到我<taskdef>说在哪里找到jacoco.jar

在类路径之前:

<taskdef resource="org/jacoco/ant/antlib.xml"/>

类路径之后:

<taskdef resource="org/jacoco/ant/antlib.xml">
   <classpath>
       <fileset dir="${basedir}/antlib/jacoco"/>
   </classpath>
</taskdef>

注意使用<fileset/>,我不在乎我的 JaCoCo jar 文件是被调用jacoco.jar还是jacoco-2.3.jar. 如果你知道 jar 的确切名称,你可以这样做:

<taskdef resource="org/jacoco/ant/antlib.xml">
   <classpath path="${basedir}/antlib/jacoco/jacoco.jar"/>
</taskdef>

并节省几行。

第 3 步:XML 命名空间

到目前为止,我不需要jacoco:为我的任务名称添加前缀。

我可以简单地这样做:

<project name="MyApp" default="package" basedir=".">

    <taskdef resource="org/jacoco/ant/antlib.xml">
        <classpath path="${basedir}/antlib/jacoco/jacoco.jar"/>
    </taskdef>

    <target name="run-coco" depends="doOtherStuff">
        <coverage>
            <!-- ... -->
        <coverage>

        <report>
            <!-- ... -->
        <report>        
    </target>
</project>

而且,你可以把它留在那里。完全没有问题。简单干净。

但是,如果您使用两个不同的 Ant 任务集,一个被调用Foo,一个被调用Bar,并且都恰好<munge/>在其中定义了一个任务,该怎么办?

<taskdef resource="org/foo/ant/antlib.xml">
    <classpath path="${basedir}/antlib/foo.jar/>
</taskdef>

<taskdef resource="org/bar/ant/antlib.xml">
    <classpath path="${basedir}/antlib/bar.jar/>
</taskdef>

<target name="munge-this">
    <!-- Is this foo.jar's or bar.jar's munge task? -->
    <munge vorbix="fester"/>
</target>

munge正在执行哪个任务?是一入foo.jar还是一入bar.jar

为了解决这个问题,Ant 允许您为每组任务定义一个XML 命名空间。您可以在最顶层的<project>实体、任务本身或名称中定义这样的命名空间<target>。99% 的时间,它是在<project>实体中完成的。

<project name="demo" default="package" basename="."
    xmlns:foo="I-will-have-fries-with-that"
    xmlns:bar="Never-on-a-first-date">

 <taskdef uri="I-will-have-fries-with-that"
    resource="org/foo/ant/antlib.xml">
    <classpath path="${basedir}/antlib/foo.jar/>
</taskdef>

<taskdef uri="Never-on-a-first-date"
    resource="org/bar/ant/antlib.xml">
    <classpath path="${basedir}/antlib/bar.jar/>
</taskdef>

<target name="munge-this">
    <!-- Look the 'foo:' XMLNS prefix! It's foo.jar's much task! -->
    <foo:munge vorbix="fester"/>
</target>

现在,我知道这是<munge/>Foo 任务列表中的任务!

定义了xmlns一个 XML 命名空间。xmlns:foo为 Foo 的任务集定义了一个命名空间,并为xmlns:barbar 的任务集定义了它。当我使用来自 的任务时foo.jar,我会在它前面加上foo:命名空间。请注意,此前缀是 . 之后的前缀xmlns:

uri只是一个与命名空间字符串匹配的字符串。我使用了字符串I-will-have-fries-with-that并向Never-on-a-first-date您展示了字符串本身并不重要。重要的是此字符串与任务的uri参数匹配<taskdef>。这样,被定义的任务就知道他们应该使用什么命名空间。

现在,通常 URI 字符串是一个 URI 定义。有两种方法可以解决这个问题:

  • 使用antlib:某种标准化的 URI,并使用命名空间的反向命名:antlib:org.jacoco.ant.
  • 使用指向项目的 URL:http://www.eclemma.org/jacoco/

我更喜欢后者,因为它会指向文档。然而,看起来第一个更受欢迎。

那么,让我们看看 Jacoco 是如何工作的:

<project name="MyApp" default="package" basedir="."
    xmlns:jacoco="antlib:org.jacoco.ant">

    <taskdef uri="antlib:org.jacoco.ant" 
        resource="org/jacoco/ant/antlib.xml">
        <classpath path="${basedir}/antlib/jacoco/jacoco.jar"/>
    </taskdef>

    <target name="run-coco" depends="doOtherStuff">
        <jacoco:coverage>
            <!-- ... -->
        <jacoco:coverage>

        <jacoco:report>
            <!-- ... -->
        <jacoco:report>        
    </target>
</project>

请注意,uriJaCoCo中的 与字符串<taskdef/>相同。xmlns:jacoco

这就是任务定义的全部内容。我希望它能解释jacoco:前缀的来源以及如何通过指向包含该类的实际类名或指向任务名称类的资源文件来定义任务。

于 2013-04-11T01:40:11.980 回答