Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define nested extension containers in Gradle

Tags:

gradle

I'm at the point in implementing my first Gradle plugin where I need to define nested extension objects. The User Guide doesn't yet cover this. There have been several questions about this in the past year or so, but I can't assemble a solution from what I've read.

This is what I believe I'd like the user to be able to do:

yang {
  yangFilesRootDir      "src/main/resources/yang"
  inspectDependencies   true

  generators {
      generator { // line 25
          generatorClassName "org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl"
          outputDir "build/gen1"
      }
      generator {
          generatorClassName "org.opendaylight.yangtools.yang.unified.doc.generator.maven.DocumentationGeneratorImpl"
          outputDir "build/gen2"
      }
  }
}

My first random stab at this looks like this, and I'm sure this isn't even close yet:

public void apply(Project project) {
    project.plugins.apply(JavaPlugin)
    YangExtension       yang    = project.extensions.create(YANG_EXT, YangExtension)
    project.yang.extensions.generators  = 
        project.container(CodeGeneratorsContainer) {
            println it
        }

Here are my extension classes:

class YangExtension {
    CodeGeneratorsContainer     generators
    String                      yangFilesRootDir
    String[]                    excludeFiles
    boolean                     inspectDependencies
    String                      yangFilesConfiguration
    String                      generatorsConfiguration
}

public class CodeGeneratorsContainer {
    Collection<CodeGenerator>   generators
}

class CodeGenerator {
    String  generatorClassName
    File    outputBaseDir
    Map     additionalConfiguration
}

When I run this right now, it fails with the following:

Caused by: java.lang.NullPointerException: Cannot get property 'name' on null object
at org.gradle.api.internal.DynamicPropertyNamer.determineName(DynamicPropertyNamer.groovy:36)
at org.gradle.api.internal.DefaultNamedDomainObjectCollection.add(DefaultNamedDomainObjectCollection.java:70)
at org.gradle.api.internal.AbstractNamedDomainObjectContainer.create(AbstractNamedDomainObjectContainer.java:58)
at org.gradle.api.internal.AbstractNamedDomainObjectContainer.create(AbstractNamedDomainObjectContainer.java:52)
at org.gradle.api.internal.NamedDomainObjectContainerConfigureDelegate._configure(NamedDomainObjectContainerConfigureDelegate.java:39)
at org.gradle.api.internal.ConfigureDelegate.invokeMethod(ConfigureDelegate.java:73)
at build_2a353qtggi869feteaqu2qgc3$_run_closure1_closure3.doCall(....\workspace2\YangUsingProject\build.gradle:25)

And I've marked line 25 in the build script, if that matters.

Update:

After integrating the solution, this is a summary of the required setup.

Sample structure in a "build.gradle" file:

yang {
    yangFilesRootDir        "src/main/resources/yang"
    //excludeFiles          "target.yang"
    inspectDependencies true

    generator {
        generatorClassName  = "org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl"
        outputDir           = "build/gen1"
    }
    generator {
        generatorClassName  = "org.opendaylight.yangtools.yang.unified.doc.generator.maven.DocumentationGeneratorImpl"
        outputDir               = "build/gen2"
    }
}

Plugin "apply" method:

public void apply(Project project) {
    project.plugins.apply(JavaPlugin)
    YangExtension       yang    = project.extensions.create(YANG_EXT, YangExtension, project)
    YangGenerateTask    task    = project.task(YANG_GENERATE_TASK, type: YangGenerateTask)
    project.afterEvaluate {
        task.init(project, yang)
    }
}

Extension class:

class YangExtension {
    private Project project

    Collection<CodeGenerator>   generators
    String                      yangFilesRootDir
    String[]                    excludeFiles
    boolean                     inspectDependencies
    String                      yangFilesConfiguration
    String                      generatorsConfiguration

    YangExtension(Project project) {
        this.project    = project
    }

    CodeGenerator generator(Closure closure) {
        def generator = project.configure(new CodeGenerator(), closure)
        generators.add(generator)
        return generator
    }
}

And finally, the CodeGenerator POGO:

class CodeGenerator {
    String  generatorClassName
    String  outputDir
    Map     additionalConfiguration
}
like image 835
David M. Karr Avatar asked Mar 11 '15 23:03

David M. Karr


1 Answers

The error is caused by the fact that Project.container() attempts to create a NamedDomainObjectContainer. This special type of collection requires to the collection type to specify a name property. An common example is sourceSets, where each item has a unique name (main, test, etc). The DSL would then look like:

generators {
   codeGenerator { 
      generatorClassName "org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl"
      outputDir "build/gen1"
   }
   docGenerator {
      generatorClassName "org.opendaylight.yangtools.yang.unified.doc.generator.maven.DocumentationGeneratorImpl"
      outputDir "build/gen2"
   }
}

If it doesn't make sense to give each generator a name, then your best bet is simply to define a method on your extension that configures a new instance of your Generator object. The catch here, is since you are instantiating the generator if won't have all the fancy decorations that Gradle would do (such as setter methods). If you want capability, you'll have to manually define those methods on the Generator class.

Update:

In order to leverage Project.container() and create a NamedDomainObjectContainer, the container type must have a property called name and a public constructor which takes a String argument for the name. The value of this property is being whatever name I use when I initially configure the object. For example:

sourceSets {
    integTest {
        srcDir 'src/integTest'
    }
}

The code above creates and configures a new SourceSet with the name "integTest" and adds it to the container. You could leverage this with your "generators" by simply adding that property. You would also want to modify your extension class as well to make the generators property of type NamedDomainObjectContainer<CodeGenerator> and there is no need for the additional CodeGeneratorsContainer class.

class YangExtension {
    NamedDomainObjectContainer<CodeGenerator>  generators
    String                      yangFilesRootDir
    String[]                    excludeFiles
    boolean                     inspectDependencies
    String                      yangFilesConfiguration
    String                      generatorsConfiguration
    
    YangExtension(Project project) {
        this.generators = project.container(CodeGenerator)
    }
}
class CodeGenerator {
    String  generatorClassName
    File    outputBaseDir
    Map     additionalConfiguration
    private String name
    
    CodeGenerator(String name) {
        this.name = name
    }
    
    String getName() {
        return this.name
    }
}

Update 2:

In case it doesn't make sense to have named object in your DSL, you could simply provide a method on your extension that configures a new CodeGenerator and adds it to a collection.

class YangExtension {
    Collection<CodeGenerator> generators = new ArrayList<>()
    private Project project

    YangExtension(Project project) {
        this.project = project
    }

    CodeGenerator generator(Closure closure) {
        def generator = project.configure(new CodeGenerator(), closure)
        generators.add(generator)
        return generator
    }
}

Your new DSL would then not have a generators block. It would simply look like this:

yang {
    generator { 
        generatorClassName = "org.opendaylight.yangtools.maven.sal.api.gen.plugin.CodeGeneratorImpl"
        outputDir = "build/gen1"
    }
    generator {
        generatorClassName = "org.opendaylight.yangtools.yang.unified.doc.generator.maven.DocumentationGeneratorImpl"
        outputDir = "build/gen2"
    }
}
like image 196
Mark Vieira Avatar answered Sep 23 '22 21:09

Mark Vieira