How to write a plugin providing an Ant task?

I would like to write a plugin that provides an Ant task, but I cannot work out how to do it. I haven’t been able to find any examples where someone has done this. Can anyone suggest a good pattern for achieving this, or point me to a working example?

I can use the task directly in the build script with the usual ant.taskdef/ant.taskname idiom, but this takes quite a lot of boilerplate that I would like to encapsulate in the plugin so that the task can be easily applied and invoked the same way in multiple projects. It sounds to me like it should be doable, but I keep hitting one problem or another and I haven’t managed it yet. I would prefer to write the plugin in Groovy or Java, but I’ll consider any approach.

You could write a task to abstract the complexity. Let’s use the xjc ant task as an example

class XjcTask extends DefaultTask {
    @Input String toolVersion = '2.2.6'
    @Input String packageName
    private Object schemaDir
    private Object bindingDir
    private Object outputDir

    @InputDirectory
    File getSchemaDir() {
         return project.file(schemaDir ?: "src/xjc/schema");
    } 

    @InputDirectory
    File getBindingDir() {
         return project.file(bindingDir ?: "src/xjc/binding");
    } 

    @OutputDirectory
    File getOutputDir() {
         return project.file(outputDir ?: "$buildDir/xjc");
    } 

    // TODO: setters

    @TaskAction
    void xjc() {
        List<Dependency> xjcDeps = [
           "com.sun.xml.bind:jaxb-xjc:$toolVersion", 
           "com.sun.xml.bind:jaxb-impl:$toolVersion", 
           "javax.xml.bind:jaxb-api:$toolVersion"
        ].collect { project.dependencies.create(it) }
        Configuration config = project.configurations.detachedConfiguration(xjcDeps)
        project.ant.taskdef(name: 'xjc', classname: 'com.sun.tools.xjc.XJCTask', classpath: config.asPath)
        project.ant.xjc(
            destdir: outputDir.absolutePath,
            package: packageName,
        ) {
            schema(dir: schemaDir.absolutePath, includes: '*.xsd')
            binding(dir: bindingDir.absolutePath, includes: '*.xjb')
            arg(value: '-verbose')
        }
    }
}

If you wanted to wrap it in a plugin you could do

class XjcPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.with {
            apply plugin: 'java'
            task xjc(type: XjcTask) { ... }
            compileJava.dependsOn xjc
        }
    }
}

Thanks for the pointer, it is a great help! I’m new to gradle and I hadn’t spotted the “detachedConfiguration” method in the API, and that was making me go round in circles.

It seems odd to me that it is an instance method of ConfigurationContainer when the instance’s state is not changed by the method call, but I guess that there is a rationale for that.

The ConfigurationContainer is the factory for Configuration instances so seems logical to me. I’m not convinced the instance`s state is not changed, it’s likely it holds a reference to the detached configuration.