Groovy DSL convention object "Could not get unknown property" - gradle

I have the following code in my build.gradle:
class GreetingPlugin implements Plugin<Project> {
def void apply(Project project) {
project.convention.plugins.greeting = new GreetingPluginConvention()
project.task('hello') {
doLast {
println project.convention.plugins.greeting.message
}
}
}
}
class GreetingPluginConvention {
String message
def greet(Closure closure) {
closure.delegate = this
closure()
}
}
apply plugin: GreetingPlugin
greet {
message = 'Hi from Gradle'
}
It executes nicely - ./gradlew hello prints "Hi from Gradle" which is expected.
However, using variable greet in the script (e.g. println greet) produces "Could not get unknown property 'greet' for project ':app' of type org.gradle.api.Project."
My question is - how the 'greet' variable is found when called against closure, but not found when used as a regular variable. What Groovy/Gradle magic is happening behind the scenes?

When it's called in a closure,
greet {
message = 'Hi from Gradle'
}
you are effectively adding more code to the original greet block/closure defined in GreetingPluginConvention, it is not a variable so attempts to treat it a such fail. Think of these block closures as a handy way to set or configure your plugins.
Gradle scripts are a bit different than your average linear script.

Related

Pass variable back from Groovy class to Gradle task

I might not have set the right title for this question and that's because i'm not that advanced in Gradle, so apologies for that.
I have the below Gradle tasks in my build.gradle:
// buildSrc/src/main/groovy/envs/actions - This where all the groovy classes which I use are located
import envs.actions.*
tasks.create("createEnvironment", CreateApplicationEnvironmentTask) {
println "executing creation"
}
return tasks.create("createRecord", CreateRecordTask) {
dependsOn "createEnvironment"
varEbCname = tasks["createEnvironment"].ebCname
}
The first task is of type "CreateApplicationEnvironmentTask", which is a class in a .groovy file where i do some actions and set some variables.
One of these variables that i set in the "CreateApplicationEnvironmentTask" class is also one called "ebCname", as seen below:
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class CreateApplicationEnvironmentTask extends DefaultTask {
String ebCname
#TaskAction
def create() {
ebCname = "some_value"
}
}
What I'm looking to do is to be able to get the value of the variable "ebCname"(which is set inside the class) from the second task "createRecord" by calling the "createEnvironment" task. I'm doing the below from within the second task, but it's not working:
varEbCname = tasks["createEnvironment"].ebCname
"varEbCname" ends up being null.
I also tried "return"-ing the "ebCname" variable from the class, but that didn't work either.
The "ebCname" variable is set in the "CreateApplicationEnvironmentTask" class which is used by the "createEnvironment" task, so I need a way get the value of that variable from the second task "createRecord".
Any idea why this isn't working and how would I go about doing this?
Thanks,
I think the main confusion here is between the gradle task life cycle phases. This is quite common when starting out with gradle. I would recommend reading through the above link to get a feel for the phases.
To illustrate here is a complete build.gradle which more or less mirrors your code:
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
task createEnvironment(type: CreateApplicationEnvironmentTask) {
println "configuring CreateApplicationEnvironmentTask"
doLast {
println "execution phase (doLast) CreateApplicationEnvironmentTask"
}
}
task createRecord(type: CreateRecordTask, dependsOn: ['createEnvironment']) {
println "configuration phase value: ${tasks["createEnvironment"].ebCname}"
doLast {
println "execution phase (doLast) value: ${tasks["createEnvironment"].ebCname}"
}
}
class CreateApplicationEnvironmentTask extends DefaultTask {
String ebCname
#TaskAction
def create() {
println "execution phase (class) CreateApplicationEnvironmentTask"
ebCname = "some_value"
}
}
class CreateRecordTask extends DefaultTask {
#TaskAction
def doit() {
println "executin phase (class) CreateRecordTask"
}
}
executing:
~> gradle createRecord
prints:
configuring CreateApplicationEnvironmentTask
configuration phase value: null
:createEnvironment
execution phase (class) CreateApplicationEnvironmentTask
execution phase (doLast) CreateApplicationEnvironmentTask
:createRecord
executin phase (class) CreateRecordTask
execution phase (doLast) value: some_value
BUILD SUCCESSFUL
when you execute any gradle task on a build.gradle file (or even just execute gradle tasks to print the available tasks), the build file is evaluated and the configuration phase code is run. This includes the code inside the curlies of any task foo { ... } definition, even if that task is not executed.
In contrast, the code inside the doLast block task foo { doLast { ... }} is executed in the execution phase.
The reason you were seeing a null value is that you set the property ebCname in your task action method (execution phase), but you are printing it in the configuration phase. Thus it has not been set yet. Either setting the value in the configuration phase (in the constructor of the class, directly when you declare the field, of inside the task declaration
task createRecord(type: CreateRecordTask...) {
ebCname = "foo"
}
) or referring to the value in the execution phase will give you a non-null value.

Creating a closure in ext

I am implementing the texturePacker task given in LibGDX's TexturePacker with gradle.
project.ext {
// ...
texturePacker = ["assets", "../android/assets", "texture"]
}
import com.badlogic.gdx.tools.texturepacker.TexturePacker
task texturePacker << {
if (project.ext.has('texturePacker')) {
logger.info "Calling TexturePacker: "+ texturePacker
TexturePacker.process(texturePacker[0], texturePacker[1], texturePacker[2])
}
}
I got it working with the suggested modifications for the classpath and added extension variable. Now I want to modify the textPacker extension variable to be a closure (Is that the right terminology?) with descriptive member names rather than an array. I tried doing this:
project.ext {
// ...
texturePacker {
inputDir = "assets"
outputDir = "../android/assets"
packFileName = "texture"
}
}
This gives the following error:
Error:Could not find method texturePacker() for arguments [build_4dusyb6n0t7j9dfuws8cc2jlu$_run_closure1$_closure7#6305684e] on project ':desktop' of type org.gradle.api.Project.
I am very new to gradle and groovy, so I have no idea what this error means. More importantly, what is the correct way to do what I want?
I suppose, closure is not the thing you need, since it's used not to store variables, but to store some executable code. By the way, if need to store it, you have to add = as follows:
project.ext {
texturePacker = {
inputDir = "assets"
outputDir = "../android/assets"
packFileName = "texture"
}
}
Anyway, if need to store variables within texturePacker variable, you rather have to use a Map type, then a Closure. This could be done like this:
project.ext {
texturePacker = [
inputDir : "assets",
outputDir : "../android/assets",
packFileName : "texture"
]
}
And then you can access this variable just by names, as:
println texturePacker.inputDir
Or, I think you can also go for implementing your own task with those properties. You can use DefaultTask which is a standard implementation of a regular task (and I'm sure it'd be enough for you);
class TexturePacker extends DefaultTask {
String inputDir; // a property - not a field!
String outputDir; // a property - not a field!
...
#TaskAction
void doSth(){
// do sth with properties above - that will be called automatically by gradle as a task-execution
}
}
task packer (type:TexturePacker) {
inputDir '<your-input-dir>'
outputDir '<your-output-dir>'
}
Syntax might not be super correct, but I think you get the idea.

gradle task method syntax in build.gradle

I am new to gradle and groovy,I am reading the usr guide of Gradle, and have some syntax questions on task method:
task intro(dependsOn: hello) {
doLast { println "I'm Gradle" }
}
Question 1:in above code, which method is called in Project API ? I know there are four overload in API:
Task task(String name, Closure configureClosure);
Task task(Map<String, ?> args, String name, Closure configureClosure);
Task task(Map<String, ?> args, String name) throws InvalidUserDataException;
Task task(String name) throws InvalidUserDataException;
but the parameter such as intro(dependsOn: hello) or copy(type: Copy) make me confused, what it should be if add parentheses?
Question 2: why << is shorthand for doLast method? I mean there is a leftshift method in Task API ? what is diff between them?
Question 3: why can use tasks.create() method in build.gradle 17.1. Defining tasks,I did not see tasks property in Project API or in AbstractProject source code.
In this particular case:
task intro(dependsOn: hello) {
doLast { println "I'm Gradle" }
}
the following method will be invoked:
Task task(Map<String, ?> args, String name, Closure configureClosure);
Since gradle uses a specific DSL it may be hard to tell but:
Q1
intro is a String name argument
dependsOn: hello which is equivalent to [dependsOn: hello] (a Map) is Map<String, ?> args
{ doLast { println "I'm Gradle" } } is Closure configureClosure
Q2
<< is a shorthand for doLast just to make it more concise. You can use doLast, <<, leftShift - it's all the same. leftShift is overridden - see here
Q3
There's no such method tasks but getTasks, see here. This is how groovy works - if method is a getter () and get can be omitted, so project.getTasks() is equivalent to project.tasks.

Multiple ways of using custom Gradle plugins in build.gradle

With reference to the Gradle docs section 59.2 I have created a simple plugin to illustrate various (seemingly working) ways to use a custom Gradle plugin's DSL exposed via plugin extensions. For example the following plugin definition and class
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// Add the 'greeting' extension object
project.extensions.create("greeting", GreetingPluginExtension)
// Add a task that uses the configuration
project.task('hello') << {
println project.greeting.message
}
}
}
class GreetingPluginExtension {
def String message = 'Hello from GreetingPlugin'
}
can be called in four ways
greeting.message 'Hi from Gradle with dot'
greeting.message = 'Hi from Gradle with dot and assigment'
greeting { message 'Hi from Gradle with block' }
greeting { message = 'Hi from Gradle with block with assignment' }
what is the correct and recommended way? Are there implications of using one way over another? In that simple example they all appear to work
As Opal said, all 4 ways are good, depending on your requirements in readability. If you have more things to configure than just the message, then the configuration block using a closure may be more convenient.
There's also a difference between using the assignment operator and omitting it: when using the assignment operator, you are explicitly setting a property, while omitting it means calling a method with that name. In that case, I prefer using the assignment operator.
You can have a look here: http://groovy-lang.org/style-guide.html#_getters_and_setters

How to use gradle extension correctly in plugins using GradleBuild task?

EDIT : I rephrased my question in taken the propositon of David M. Karr into account.
I am writing a gradle plugin. This plugin is launching a task extending GradleBuild. The external gradle build file needs some info as parameters. These parameters are given in project extension.
Plugin code
class MyPlugin implements Plugin<Project> {
def mExt
void apply(Project project) {
mExt = project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
def param = new StartParameter()
param.setProjectProperties([target:getTarget()])
// Problem here
startParameter = param
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
}
def getMyBuildPath(){
...
}
// Problem here
def getTarget(){
return {mExt.target}
}
}
class MyExt {
def String target = "uninitialised"
}
Gradle build file :
apply plugin : 'com.example.myplugin'
ext{
target = "myTarget"
}
External Gradle build file :
task build(){
println project.target
}
If I put a closure in getTarget(), println project.target shows the closure and not the string.
If I don't put the closure :
// Problem here
def getTarget(){
return mExt.target
}
Then I got "uninitialised" instead of "myTarget".
How can I get the value of myext.target here ?
I am using gradle 2.3
Try this:
Define an instance variable called "myext", of type "MyExt".
In the "apply" method, do this:
myext = project.extensions.create('myext',MyExt)
In the "getTarget" method, return "myext.target".
I have succeeded in getting what I wanted to in using project.afterEvaluate method. Thanks to this question
1) In gradle build task, startParameter.projectProperties is waiting for a map, not a closure. So the idea to put a closure for a lazy definition cannot work.
2) If I put directly in my plugin a reference to mExt.target or project.myext.target, then the initial value is set. The value put in my build.gradle file is not used because the plugin is already evaluated.
3) project.afterEvaluate() solve my problem. The closure ends configuring myTask in using the project's extension.
void apply(Project project) {
project.extensions.create('myext',MyExt)
project.task('myTask', type:GradleBuild){
buildFile = getMyBuildPath()
tasks = [
'build',
'generateDebugJavadocJar'
]
}
project.afterEvaluate { proj ->
proj.myTask.startParameter.projectProperties = [target:proj.myext.target]
}
}

Resources