My comments illustrate points of confusion, The multiple uses of the same identifier make this example more confusing than illustrative.
apply plugin: DocumentationPlugin
books { /* books #1 - I think this is the "collection" referred to in the text*/
quickStart {
sourceFile = file('src/docs/quick-start')
}
userGuide {
}
developerGuide {
}
}
task books << { /* books #2 a task called books */
books.each { book -> /* which "books" is being iterated? */
println "$book.name -> $book.sourceFile"
}
}
class DocumentationPlugin implements Plugin<Project> {
void apply(Project project) {
/* books #3 - a local variable named books */
def books = project.container(Book)
books.all {
/* for each book in "books", define sourceFile.
But which "books" is being iterated - #1 or #3? */
sourceFile = project.file("src/docs/$name")
}
project.extensions.books = books
/* Left of = sign: books #4?
Right of = sign: is #1 or #3 being assigned? */
}
}
class Book {
final String name
File sourceFile
Book(String name) {
this.name = name
}
}
I’m going to take a stab at answering this. Someone else please verify this and amend where I’m not specific enough.
I’m not going to directly address your #1-#4 references, but hopefully my explanation will help.
The first “books” block does indeed represent a collection of Book objects.
Giving the task the name “books” is definitely confusing, but the reference to “books” in the body of the task doesn’t refer to the task name. For what it’s worth, that refers to the collection defined by the earlier “books” block.
In the “apply” method, the call to “project.container(Book)” creates a NamedDomainObjectContainer. This is indeed stored in a local variable of the method. Ignoring the “all” block for a moment, note that after this block the local variable is assigned to “project.extensions.books”, which effectively connects the declaration of a “books” block in the build script to this NDOC.
What is really surprising is what the “books.all” block does. Anyone looking at the previous assignment and this “books.all” block would think “you’ve just created an empty container, and now you’re iterating through it”. Duh. That’s not going to do anything useful. The reality is that NamedDomainObjectContainer has some magic that this section in the doc, and in the DSL doc, fails to mention. Essentially, the “books.all” closure will be executed whenever a Book is added to the container, at any time in the future.
To help understand the flow of this, it would be helpful to define the plugin in a standalone Groovy file in the “buildSrc” tree (gotten to that section yet? ), and then run the Gradle build in “debug” mode (lets you step through the code in the debugger, although not the build script itself). I don’t remember the syntax for that, however.
I had to modify the source with lots of printlns to show me what was happening when. After doing that, and then rereading your explanation, it finally makes sense to me. The names are not the only confusing thing here. What’s also confusing is that we are defining behavior that doesn’t happen when it appears to, based on the “normal” flow of the code but later.
Thanks. Here is my modified source
apply plugin: DocumentationPlugin
books {
println "initializing the books object"
quickStart {
println "initialized the quickStart object"
sourceFile = file('src/docs/quick-start')
println "changed quickStart.sourceFile"
println " to $quickStart.sourceFile"
}
userGuide {
println "initialized the userGuide object"
}
developerGuide {
println "initialized the developerGuide object"
}
}
task books << {
books.each { book ->
println "$book.name -> $book.sourceFile"
}
}
class DocumentationPlugin implements Plugin<Project> {
void apply(Project project) {
println("in apply()");
def lbooks = project.container(Book)
lbooks.all {
sourceFile = project.file("src/docs/$name")
println "applying lbooks.all defined in DocumentationPlugin:"
println " set sourceFile of book $name to $sourceFile"
}
project.extensions.books = lbooks
println "exiting apply()"
}
}
class Book {
final String name
File sourceFile
/*
constructor for Book object, obeying the "contract" prescribed for objects used with the
NamedDomainObjectContainer
*/
Book(String name) {
println("Book constructor($name)")
this.name = name
}
}
And here is the output, which shows what happens when:
$ gradle books
in apply()
exiting apply()
initializing the books object
Book constructor(quickStart)
applying lbooks.all defined in DocumentationPlugin:
set sourceFile of book quickStart to /home/user/examples/books/src/docs/quickStart
initialized the quickStart object
changed quickStart.sourceFile to
/home/user/examples/books/src/docs/quick-start
Book constructor(userGuide)
applying lbooks.all defined in DocumentationPlugin:
set sourceFile of book userGuide to /home/user/examples/books/src/docs/userGuide
initialized the userGuide object
Book constructor(developerGuide)
applying lbooks.all defined in DocumentationPlugin:
set sourceFile of book developerGuide to /home/user/examples/books/src/docs/developerGuide
initialized the developerGuide object
:books
developerGuide -> /home/user/examples/books/src/docs/developerGuide
quickStart -> /home/user/examples/books/src/docs/quick-start
userGuide -> /home/user/examples/books/src/docs/userGuide
BUILD SUCCESSFUL
Total time: 0.673 secs