Gradle 提供了与 Ant 的出色集成。你可以在 Gradle 构建中使用单独的 Ant 任务或整个 Ant 构建。事实上,你会发现,在 Gradle 构建脚本中使用 Ant 任务比使用 Ant 的 XML 格式要容易得多、功能也更强大。你甚至可以简单地将 Gradle 用作一个功能强大的 Ant 任务脚本工具。

Ant 可以分为两层。第一层是 Ant 语言。它为 build.xml 文件提供语法、处理目标、宏定义等特殊构造。换句话说,除了 Ant 任务和类型之外的一切。Gradle 理解这种语言,并允许你将 Ant build.xml 直接导入 Gradle 项目。然后,你可以像使用 Gradle 任务一样使用 Ant 构建的目标。

Ant 的第二层是它丰富的 Ant 任务和类型,如 javaccopyjar。对于这一层,Gradle 通过依赖 Groovy 和出色的 AntBuilder 提供集成。

最后,由于构建脚本是 Groovy 脚本,你始终可以将 Ant 构建作为外部进程执行。你的构建脚本可能包含以下语句:"ant clean compile".execute().[1]

你可以将 Gradle 的 Ant 集成用作将构建从 Ant 迁移到 Gradle 的路径。例如,你可以从导入现有的 Ant 构建开始。然后,你可以将依赖项声明从 Ant 脚本移到构建文件中。最后,你可以将任务移到构建文件中,或用 Gradle 的一些插件替换它们。此过程可以分阶段完成,并且在整个过程中都可以进行 Gradle 构建。

Ant 集成与配置缓存不完全兼容。使用Task.ant在任务操作中运行 Ant 任务可能有效,但导入 Ant 构建不受支持。

在构建中使用 Ant 任务和类型

在构建脚本中,Gradle 提供了一个名为 ant 的属性。这是对 AntBuilder 实例的引用。此 AntBuilder 用于从构建脚本访问 Ant 任务、类型和属性。Ant 的 build.xml 格式与 Groovy 之间有一个非常简单的映射,如下所述。

通过调用 AntBuilder 实例上的方法来执行 Ant 任务。使用任务名称作为方法名称。例如,通过调用 ant.echo() 方法执行 Ant echo 任务。Ant 任务的属性作为 Map 参数传递给该方法。以下是 echo 任务的示例。请注意,我们还可以混合使用 Groovy 代码和 Ant 任务标记。这可能会非常强大。

build.gradle.kts
tasks.register("hello") {
    doLast {
        val greeting = "hello from Ant"
        ant.withGroovyBuilder {
            "echo"("message" to greeting)
        }
    }
}
build.gradle
tasks.register('hello') {
    doLast {
        String greeting = 'hello from Ant'
        ant.echo(message: greeting)
    }
}
gradle hello 的输出
> gradle hello

> Task :hello
[ant:echo] hello from Ant

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

通过将嵌套文本作为任务方法调用的参数来传递给 Ant 任务。在此示例中,我们将 echo 任务的消息作为嵌套文本传递

build.gradle.kts
tasks.register("hello") {
    doLast {
        ant.withGroovyBuilder {
            "echo"("message" to "hello from Ant")
        }
    }
}
build.gradle
tasks.register('hello') {
    doLast {
        ant.echo('hello from Ant')
    }
}
gradle hello 的输出
> gradle hello

> Task :hello
[ant:echo] hello from Ant

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

将嵌套元素传递给 Ant 任务时,将其放在闭包中。嵌套元素的定义方式与任务相同,即调用与我们要定义的元素同名的某个方法。

build.gradle.kts
tasks.register("zip") {
    doLast {
        ant.withGroovyBuilder {
            "zip"("destfile" to "archive.zip") {
                "fileset"("dir" to "src") {
                    "include"("name" to "**.xml")
                    "exclude"("name" to "**.java")
                }
            }
        }
    }
}
build.gradle
tasks.register('zip') {
    doLast {
        ant.zip(destfile: 'archive.zip') {
            fileset(dir: 'src') {
                include(name: '**.xml')
                exclude(name: '**.java')
            }
        }
    }
}

可以使用与访问任务相同的方式访问 Ant 类型,即使用类型名称作为方法名称。方法调用返回 Ant 数据类型,然后可以在构建脚本中直接使用该类型。在以下示例中,我们创建了一个 Ant path 对象,然后遍历其内容。

build.gradle.kts
import org.apache.tools.ant.types.Path

tasks.register("list") {
    doLast {
        val path = ant.withGroovyBuilder {
            "path" {
                "fileset"("dir" to "libs", "includes" to "*.jar")
            }
        } as Path
        path.list().forEach {
            println(it)
        }
    }
}
build.gradle
tasks.register('list') {
    doLast {
        def path = ant.path {
            fileset(dir: 'libs', includes: '*.jar')
        }
        path.list().each {
            println it
        }
    }
}

有关 AntBuilder 的更多信息,请参阅“Groovy in Action”8.4 或 Groovy Wiki

在构建中使用自定义 Ant 任务

若要让自定义任务在构建中可用,可以使用 taskdef(通常更简单)或 typedef Ant 任务,就像在 build.xml 文件中一样。然后,可以像引用内置 Ant 任务一样引用自定义 Ant 任务。

build.gradle.kts
tasks.register("check") {
    val checkstyleConfig = file("checkstyle.xml")
    doLast {
        ant.withGroovyBuilder {
            "taskdef"("resource" to "com/puppycrawl/tools/checkstyle/ant/checkstyle-ant-task.properties") {
                "classpath" {
                    "fileset"("dir" to "libs", "includes" to "*.jar")
                }
            }
            "checkstyle"("config" to checkstyleConfig) {
                "fileset"("dir" to "src")
            }
        }
    }
}
build.gradle
tasks.register('check') {
    def checkstyleConfig = file('checkstyle.xml')
    doLast {
        ant.taskdef(resource: 'com/puppycrawl/tools/checkstyle/ant/checkstyle-ant-task.properties') {
            classpath {
                fileset(dir: 'libs', includes: '*.jar')
            }
        }
        ant.checkstyle(config: checkstyleConfig) {
            fileset(dir: 'src')
        }
    }
}

你可以使用 Gradle 的依赖管理来组装要用于自定义任务的类路径。为此,你需要为类路径定义一个自定义配置,然后向配置添加一些依赖项。这在 声明依赖项 中有更详细的描述。

build.gradle.kts
val pmd = configurations.create("pmd")

dependencies {
    pmd(group = "pmd", name = "pmd", version = "4.2.5")
}
build.gradle
configurations {
    pmd
}

dependencies {
    pmd group: 'pmd', name: 'pmd', version: '4.2.5'
}

要使用类路径配置,请使用自定义配置的 asPath 属性。

build.gradle.kts
tasks.register("check") {
    doLast {
        ant.withGroovyBuilder {
            "taskdef"("name" to "pmd",
                      "classname" to "net.sourceforge.pmd.ant.PMDTask",
                      "classpath" to pmd.asPath)
            "pmd"("shortFilenames" to true,
                  "failonruleviolation" to true,
                  "rulesetfiles" to file("pmd-rules.xml").toURI().toString()) {
                "formatter"("type" to "text", "toConsole" to "true")
                "fileset"("dir" to "src")
            }
        }
    }
}
build.gradle
tasks.register('check') {
    doLast {
        ant.taskdef(name: 'pmd',
                    classname: 'net.sourceforge.pmd.ant.PMDTask',
                    classpath: configurations.pmd.asPath)
        ant.pmd(shortFilenames: 'true',
                failonruleviolation: 'true',
                rulesetfiles: file('pmd-rules.xml').toURI().toString()) {
            formatter(type: 'text', toConsole: 'true')
            fileset(dir: 'src')
        }
    }
}

导入 Ant 构建

你可以使用 ant.importBuild() 方法将 Ant 构建导入到你的 Gradle 项目中。当你导入 Ant 构建时,每个 Ant 目标都将被视为一个 Gradle 任务。这意味着你可以像操作 Gradle 任务一样操作和执行 Ant 目标。

build.gradle.kts
ant.importBuild("build.xml")
build.gradle
ant.importBuild 'build.xml'
build.xml
<project>
    <target name="hello">
        <echo>Hello, from Ant</echo>
    </target>
</project>
gradle hello 的输出
> gradle hello

> Task :hello
[ant:echo] Hello, from Ant

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

你可以添加一个依赖于 Ant 目标的任务

build.gradle.kts
ant.importBuild("build.xml")

tasks.register("intro") {
    dependsOn("hello")
    doLast {
        println("Hello, from Gradle")
    }
}
build.gradle
ant.importBuild 'build.xml'

tasks.register('intro') {
    dependsOn("hello")
    doLast {
        println 'Hello, from Gradle'
    }
}
gradle intro 的输出
> gradle intro

> Task :hello
[ant:echo] Hello, from Ant

> Task :intro
Hello, from Gradle

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

或者,你可以向 Ant 目标添加行为

build.gradle.kts
ant.importBuild("build.xml")

tasks.named("hello") {
    doLast {
        println("Hello, from Gradle")
    }
}
build.gradle
ant.importBuild 'build.xml'

hello {
    doLast {
        println 'Hello, from Gradle'
    }
}
gradle hello 的输出
> gradle hello

> Task :hello
[ant:echo] Hello, from Ant
Hello, from Gradle

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

Ant 目标也可以依赖于 Gradle 任务

build.gradle.kts
ant.importBuild("build.xml")

tasks.register("intro") {
    doLast {
        println("Hello, from Gradle")
    }
}
build.gradle
ant.importBuild 'build.xml'

tasks.register('intro') {
    doLast {
        println 'Hello, from Gradle'
    }
}
build.xml
<project>
    <target name="hello" depends="intro">
        <echo>Hello, from Ant</echo>
    </target>
</project>
gradle hello 的输出
> gradle hello

> Task :intro
Hello, from Gradle

> Task :hello
[ant:echo] Hello, from Ant

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

有时可能需要“重命名”为 Ant 目标生成的 task,以避免与现有的 Gradle 任务发生命名冲突。为此,请使用 AntBuilder.importBuild(java.lang.Object, org.gradle.api.Transformer) 方法。

build.gradle.kts
ant.importBuild("build.xml") { antTargetName ->
    "a-" + antTargetName
}
build.gradle
ant.importBuild('build.xml') { antTargetName ->
    'a-' + antTargetName
}
build.xml
<project>
    <target name="hello">
        <echo>Hello, from Ant</echo>
    </target>
</project>
gradle a-hello 的输出
> gradle a-hello

> Task :a-hello
[ant:echo] Hello, from Ant

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

请注意,虽然此方法的第二个参数应该是 Transformer,但在 Groovy 中编程时,我们可以简单地使用闭包而不是匿名内部类(或类似类),因为 Groovy 支持将闭包自动强制转换为单抽象方法类型

Ant 属性和引用

有几种方法可以设置 Ant 属性,以便 Ant 任务可以使用该属性。你可以在 AntBuilder 实例上直接设置属性。Ant 属性也可用作你可以更改的 Map。你还可以使用 Ant property 任务。以下是有关如何执行此操作的一些示例。

build.gradle.kts
ant.setProperty("buildDir", buildDir)
ant.properties.set("buildDir", buildDir)
ant.properties["buildDir"] = buildDir
ant.withGroovyBuilder {
    "property"("name" to "buildDir", "location" to "buildDir")
}
build.gradle
ant.buildDir = buildDir
ant.properties.buildDir = buildDir
ant.properties['buildDir'] = buildDir
ant.property(name: 'buildDir', location: buildDir)

许多 Ant 任务在执行时设置属性。有几种方法可以获取这些属性的值。你可以直接从 AntBuilder 实例获取属性。Ant 属性也可用作 Map。以下是一些示例。

build.xml
<property name="antProp" value="a property defined in an Ant build"/>
build.gradle.kts
println(ant.getProperty("antProp"))
println(ant.properties.get("antProp"))
println(ant.properties["antProp"])
build.gradle
println ant.antProp
println ant.properties.antProp
println ant.properties['antProp']

设置 Ant 引用有多种方法

build.gradle.kts
ant.withGroovyBuilder { "path"("id" to "classpath", "location" to "libs") }
ant.references.set("classpath", ant.withGroovyBuilder { "path"("location" to "libs") })
ant.references["classpath"] = ant.withGroovyBuilder { "path"("location" to "libs") }
build.gradle
ant.path(id: 'classpath', location: 'libs')
ant.references.classpath = ant.path(location: 'libs')
ant.references['classpath'] = ant.path(location: 'libs')
build.xml
<path refid="classpath"/>

获取 Ant 引用有多种方法

build.xml
<path id="antPath" location="libs"/>
build.gradle.kts
println(ant.references.get("antPath"))
println(ant.references["antPath"])
build.gradle
println ant.references.antPath
println ant.references['antPath']

Ant 日志记录

Gradle 将 Ant 消息优先级映射到 Gradle 日志级别,以便从 Ant 记录的消息显示在 Gradle 输出中。默认情况下,这些映射如下

表 1. Ant 消息优先级映射
Ant 消息优先级 Gradle 日志级别

VERBOSE

DEBUG

DEBUG

DEBUG

INFO

INFO

WARN

WARN

ERROR

ERROR

微调 Ant 日志记录

Ant 消息优先级到 Gradle 日志级别的默认映射有时会出现问题。例如,没有消息优先级直接映射到 LIFECYCLE 日志级别,这是 Gradle 的默认值。许多 Ant 任务以 INFO 优先级记录消息,这意味着要从 Gradle 公开这些消息,必须以将日志级别设置为 INFO 的方式运行构建,这可能会记录比所需更多的输出。

相反,如果 Ant 任务以过高的级别记录消息,则要禁止这些消息,需要以更高的日志级别(如 QUIET)运行构建。但是,这可能会导致禁止其他需要的输出。

为了解决此问题,Gradle 允许用户微调 Ant 日志记录并控制消息优先级到 Gradle 日志级别的映射。这是通过使用 AntBuilder.setLifecycleLogLevel(java.lang.String) 方法设置应映射到默认 Gradle LIFECYCLE 日志级别的优先级来完成的。设置此值时,以配置的优先级或更高优先级记录的任何 Ant 消息都将至少记录在 LIFECYCLE 中。以低于此优先级记录的任何 Ant 消息都将最多记录在 INFO 中。

例如,以下内容将更改映射,以便在 LIFECYCLE 日志级别公开 Ant INFO 优先级消息。

build.gradle.kts
ant.lifecycleLogLevel = AntBuilder.AntMessagePriority.INFO

tasks.register("hello") {
    doLast {
        ant.withGroovyBuilder {
            "echo"("level" to "info", "message" to "hello from info priority!")
        }
    }
}
build.gradle
ant.lifecycleLogLevel = "INFO"

tasks.register('hello') {
    doLast {
        ant.echo(level: "info", message: "hello from info priority!")
    }
}
gradle hello 的输出
> gradle hello

> Task :hello
[ant:echo] hello from info priority!

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

另一方面,如果 lifecycleLogLevel 设置为 ERROR,则以 WARN 优先级记录的 Ant 消息将不再记录在 WARN 日志级别。它们现在将记录在 INFO 级别,并且默认情况下将被禁止。

API

Ant 集成由 AntBuilder 提供。


1. 在 Groovy 中,你可以执行字符串。要详细了解如何使用 Groovy 执行外部进程,请参阅“Groovy 实战”9.3.2 或 Groovy wiki