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
}
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"
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With