Logic in gradle/groovy scripting - gradle

I am new in groovy-gradle field, and right now I am taking multiple online courses, but I am missing something here.
I'll give you and example. This creates a Jar file:
apply plugin: 'java' // 1. Apply the Java plugin to the project
sourceSets {
main {
java {
srcDir 'java' // 3. Add 'java' directory as a source directory
}
}
}
jar {
manifest {
attributes 'Implementation-Version': '1.0' // 2. Add manifest attribute
}
}
You can find this solution everywhere, but not clear explanation.
Now, can apply plugin by: Plugin.apply(T)
I assume that the Plugin is object instance and apply is its method and T is an an argument.
So what is what is apply plugin "java"?
There is also thing called sourceSets. It may be method that takes an argument of a Closure, or it may be a property that takes a Closure as argument because of default getter generated by groovy.
I canot tell because in groovy equal sign is optional, parenthesis are optional.--- VERY INOVATIVE!!!!!!!!
And finally there is thing called main. I cannot find what it is, and I've been looking for it everywhere, even here :https://docs.gradle.org/current/dsl/org.gradle.api.tasks.SourceSet.html
And this 'main'-ting contains thing called java (looks like instance of SourceDirectorySet), which contains method srcDir, that takes a string as an agument.
Does it makes sense to you?.
How to extract information from here:
https://docs.gradle.org/current/dsl/, and use it in a build?
What am I missing here?

I'm on my mobile so it's difficult to explain all of the Gradle magic but this should help you on your way.
There is an implicit Project instance in scope and much of what you see in a gradle script will delegate to this.
Eg
apply plugin: 'java'
Is equivalent to
getProject().apply([plugin: 'java'])
You can read more in the writing a custom plugin section but there's a property file that maps "java" to JavaPlugin
When you applied the "java" plugin, this will "mixin" the JavaPluginConvention to the project (so you can now call getSourceSets() in java which can be shortened to "sourceSets" in groovy)
One feature that gets a lot of use in Gradle's DSL is Groovy's methodMissing(...). So
sourceSets.main { ... }
will actually get dynamically delegated to
sourceSets.getByName('main').configure { ... }
Another feature which is a source of "magic" is that in groovy you can write
def a = new A()
a.foo = 'bar'
a.baz('x')
As
def a = new A()
a.with {
foo = 'bar'
baz('x')
}
Hopefully you can see that all property/method references in the closure are delegated to the "a" instance. This style of configuration helps gradle scripts remain succinct.
See the with method

Related

What does it mean in Groovy to specify a property followed by a closure?

I am completely new to Groovy, trying to learn it, but stymied because I can't parse the syntax well enough to even know where to look in the documentation. I am using Groovy in Gradle. There are many places where examples are given, but no explanation on what it means, so I just need a few pointers.
publishing {
publications {
mavenJava(MavenPublication) {
groupId = 'com.xxx.yyy'
artifactId = 'zzz'
from components.java
}
}
repositories {
mavenLocal();
}
}
The main build code is referring to things on the project class. On that class, I can find a property called publishing, and it is a class PublishingExtension. It appears then that the curly brace starts a closure with code in it. The documentation says this syntax:
publishing { }
configures the PublishingExtension. What I want to understand is what it means (i.e. what is actually happening) when I specify what looks like a property and follow that with a Closure. In the Groovy documentation I could not find any syntax like this, nor explanation. I sure it is something simple but I don't know enough to even know what to look for.
If I visit the Project Class API Docs there is no method there named publishing. Nor is there a property defined by the method getPublishing. Apparently this magic capability is enabled by the publishing plugin. If I visit the Publishing Plugin API Doc there is no description of this publishing property either or how it modifies the base project.
Similarly, drilling down a little more, that closure starts with a symbol publications and in the documentation for the PublishingExtension I find a property which is of type PublicationContainer which is read only. I also find a method named publications which does not accept a closure, but instead a configuration of type Action<? super PublicationContainer>. Again, I don't know how the contents of the curly braces are converted to an Action class instance. How does this object get constructed? Action is an interface, and the only method is execute however it is completely unclear how this action gets constructed.
The block that defines the Action starts with symbol mavenJava that looks like a method, but actually that first symbol is declaring a name of a new object of type MavenPublication named mavenJava. Either this is magically constructed (I don't know the rules) or there is a method called, but which method? What is it about PublicationContainer that allows it to know that an arbitrary mavenJava command is supposed to create an object instance. Then again, the curly braces that follow this, is that a closure, a configuration, or something even more exotic?
So as you can see, I am missing a little info on how Groovy works. So far I can't find documentation that explains this syntax, however it might be there if I knew what to look for. Can anyone explain what the syntax is really doing, or refer me to a site that can explain it?
publishing is called to configure the PublishingExtension.
In PublishingExtension there is a publications method accepting an Action which is usually coerced from a Closure. In Groovy a Closure is automatically converted to an interface with a single method.
mavenJava is a non-existent DSL method, which is passed by Gradle DSL builder to the create method of PublicationContainer:
publishing.publications.create('mavenJava', MavenPublication) {
// Configure the maven publication here
}
groupId and artifactId are properties of MavenPublication and are being set here.
from is the from(component) of MavenPublication and is written using Groovy simplified method call literal without brackets.
In general Gradle uses a root DSL builder which calls the nested DSL builders provided by plugins. Hence sometimes it's difficuilt (also for the IDE) to find proper references of all parts of the build.gradle file.

Get the project instance from kotlin's gradle

I'm trying to migrate a script from Groovy's gradle to Kotlin's gradle.
The function:
def Group(Closure closure) {
closure.delegate = dependencies
return closure
}
ext {
aGroup = Group {
implementation xyz
kapt xpto
...
}
...
}
What I manage to do:
object Group {
operator fun <T> invoke(project: Project, closure: Closure<T>):Closure<T> {
closure.delegate = project.dependencies
return closure
}
}
Notice that I've added the extra arg Project.
Groovy access the project property directly, I would like to know how can I get this directly from kotlin too, so I'll be able to remove that extra arg and keep the previous syntax.
Short answer: You can't
Long answer:
You can't due to the strong/statically typed nature of Kotlin (and Java). Groovy is a dynamic language and lets you get away with many things, to an extent. In addition to the dynamic nature of Groovy, Gradle implements a lot of the metaprogramming that Groovy offers.
So even though you do not explicitly pass in a Project in your Groovy script, behind the scenes it is being looked up dynamically.
You could make Group an extension of Project to have a reference of project.

Are gradle scripts written in groovy?

I have trouble understanding the groovy syntax in gradle.
If named parameters (in groovy) are using the : suffix, then I assume that the code apply plugin: 'java' means to call the function apply(plugin = 'java'). This is strange because the function apply is not even defined. The following gives me an error in my gradle script:
println apply.getClass()
> Could not get unknown property 'apply' for root project 'Simple' of type
org.gradle.api.Project.
So what is apply and where is it defined? Why doesn't the code above just print the class of the apply element?
And one other thing that is strange to me is the following:
dependencies {
compile 'org.slf4j:slf4j-api:1.7.12'
testCompile 'junit:junit:4.12'
}
The syntax suggests that the code wrapped in {} is a closure, but what are the compile and testCompile elements? If it was a closure, then the code above would just return 'junit:junit:4.12' as a string and the rest should fail to compile. It looks like it's more a definition of a map. But again, if the code above is data, then I should be able to enter it at the groovysh shell.
groovy:000> dependencies {
compile 'org.slf4j:slf4j-api:1.7.12'
testCompile 'junit:junit:4.12'
}
groovy:001> groovy:002> groovy:003> ERROR groovy.lang.MissingMethodException:
No signature of method: groovysh_evaluate.dependencies() is applicable for argument types: (groovysh_evaluate$_run_closure1) values: [groovysh_evaluate$_run_closure1#b7c4869]
This is confusing to me. I thought that gradle scripts are just groovy scripts, but it seems that the gradle DSL adds element to the groovy language. A groovy clojure becomes a map, a function call with named parameters becomes something different.
Can someone enlighten me on this groovy DSL ;)
plugin: 'java'
is a groovy map. See the Project.apply() documentation and the explanation for this syntax in the groovy documentation.
Regarding dependencies, see DependencyHandler.
Groovy is a very dynamic language, where you can actually call non-declared methods and have a handler do something based on the called method name. AFAIK, that's the trick used here. See the source code.
I'm not a groovy developer, and although I find the DSL elegant, I also find it confusing at times, because I find it hard to link some parts of the DSL to concrete methods in the documentation. But you end up understanding it and getting used to it.
compile 'org.slf4j:slf4j-api:1.7.12' is not a map, it's a method invocation. In Groovy you can omit brackets, so the call is equivalent to
compile( 'org.slf4j:slf4j-api:1.7.12' )
Also such methods in Gradle can take the second argument:
compile( 'org.slf4j:slf4j-api:1.7.12' ){ exclude module:'log4j' }
In this case, the module:'log4j' is a map with omitted square brackets, and the call can be rewritten as
compile( 'org.slf4j:slf4j-api:1.7.12' ){ exclude( [module:'log4j'] ) }

Where do the Gradle build-in functions come from?

I am reading gradle book and it says:
apply plugin: 'java'
task customJar(type: Jar) {
...
destinationDir = file("${buildDir}/jars")
...
}
The file() method, which is always available inside a Gradle
build file
So my question is where does this method come from? Where is it defined and why is it visible to the script?
file() method comes from Project class (as many other methods). Basically if there's no object defined before method invocation there's a big likelihood that this method is invoked on project object which is a Project class instance.

How to move a gradle function from build.gradle into a plugin?

Currently, I have a few utility functions defined in the top level build.gradle in a multi-project setup, for example like this:
def utilityMethod() {
doSomethingWith(project) // project is magically defined
}
I would like to move this code into a plugin, which will make the utilityMethod available within a project that applies the plugin. How do I do that? Is it a project.extension?
This seems to work using:
import org.gradle.api.Plugin
import org.gradle.api.Project
class FooPlugin implements Plugin<Project> {
void apply(Project target) {
target.extensions.create("foo", FooExtension)
target.task('sometask', type: GreetingTask)
}
}
class FooExtension{
def sayHello(String text) {
println "Hello " + text
}
}
Then in the client build.gradle file you can do this:
task HelloTask << {
foo.sayHello("DOM")
}
c:\plugintest>gradle -q HelloTask
Hello DOM
https://docs.gradle.org/current/userguide/custom_plugins.html
I implemented this recently, a full example is available at Github.
The injection basically boils down to
target.ext.utilityMethod = SomeClass.&utilityMethod
Beware:
This method could potentially conflict with some other plugin, so you should consider whether to use static imports instead.
Based on Answer 23290820.
Plugins are not meant to provide common methods but tasks.
When it comes to extensions they should be used to gather input for the applied plugins:
Most plugins need to obtain some configuration from the build script.
One method for doing this is to use extension objects.
More details here.
Have a look at Peter's answer, using closures carried via ext might be what you are looking for.

Resources