Trying to understand gradle project properties - gradle

Clearly I don't understand what's going on here.
I guess prop2 and prop3 can't be accessed because they are variables instead of "project properties".
The question arose because I would like the variables prop2 and prop3 to be visible from within the "doTheThing()" method, but I don't want to have to pass them in. I want the variables to be globally accessible to tasks, methods and classes (but only from within in the build script itself) - and I want them to be typed (which is why the defintion of prop1 is not acceptable).
Really, though - I guess what I'm asking for is some help understanding what a Gradle project property is and what the syntax 'prop1 = "blah"' is actually doing.
I have read the Gradle user guide and also Gradle in Action - if they already explain this concept please point me to the right section (maybe I glossed over it at the time not understanding what is was saying).
prop1 = "blah"
String prop2 = "bleah"
def prop3 = "blargh"
task testPropAccess << {
println "1: $prop1"
println "2: $prop2"
println "3: $prop3"
doTheThing()
}
private void doTheThing(){
println "4: $prop1"
println "5: $prop2" // error: Could not find property 'prop2' on root project 'script'
println "6: $prop3" // error: Could not find property 'prop3' on root project 'script'
}

When you declare a variable at the outermost level (as in your second and third statement), it becomes a local variable of the script's run method. This is really just Groovy behavior, and nothing that Gradle can easily change.
If you want the equivalent of a global variable, just assign a value to an unbound variable (as in your first statement). This adds a dynamic property to Gradle's Project object, which is visible throughout the build script (unless shadowed). In other words, prop1 = "blah" is equivalent to project.prop1 = "blah".
If you want the equivalent of a typed global variable, you'll have to wait until Gradle upgrades to Groovy 1.8, which makes this possible with the #Field annotation. Or you write a plugin that mixes a convention object into the Project object (but that's not suitable for ad-hoc scripting).

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.

Declaring gradle dependencies with arguments

I have a multi-moduled Gradle project setup. I would like for a module to be able to accept an argument from a module that specifies it as a dependency. Can this be achieved?
Something like this in the parent module:
dependencies {
implementation project(':innerModule') {
arg "foo"
}
}
And the inner module would also have to declare the variable that gets assigned. def arg = ""? Really not sure on the correct syntax here.
EDIT:
What I am trying to do is create a build type variable, which I can then use for conditional logic in my project code. It is an Android project, but the one module it requires is non-Android. So I cannot use the buildTypes closure in that module, but I was hoping to feed a variable into the non-Android module once the parent Android module determines the build type. I feel like that should be possible
The answer to your specific question is no.
However, if you describe what you are trying to achieve, maybe Gradle will be able to help you.
What is this variable controlling?
What is the effect of the different values?

How Gradle tasks inline configuration works under the hood ?

I'm trying to understand how gradle build script is interpreted under the hood.
I get that the entire build script delegates to the project object, so when we type
task taskName
we are executing a function defined in the project object with task as name and taking a string parameter.
What is weird for me is the inline configuration of the task :
taskName.description = "Description of the task"
taskName.group = "Group of the task"
How this really works in the level of project object ?
Take a look at https://docs.gradle.org/current/dsl/org.gradle.api.Project.html, the first "Properties" section.
Here are the useful bits with some of the details removed:
A project has 5 property 'scopes', which it searches for properties. The scopes are:
The Project object itself.
The extra properties of the project.
The extensions added to the project by the plugins.
The tasks of the project.
The extra properties and convention properties inherited from the project's parent, recursively up to the root project. The properties of this scope are read-only.
When reading a property, the project searches the above scopes in order, and returns the value from the first scope it finds the property in. If not found, an exception is thrown.
In your example, taskName.description = "..." means that Gradle looks up taskName as a method on Project, as an extra property, as an extension and then it finds a task with that name. Then you're just calling setDescription on a regular Task object.
The magic happens in Project.property() using a ExtensibleDynamicObject configured like so:
https://github.com/gradle/gradle/blob/0b9cb4429513297e03965b0578607d10a2a1fcdf/subprojects/core/src/main/java/org/gradle/api/internal/project/DefaultProject.java#L226-L230

Gradle custom task action order

I'm looking at a simple example of a custom task in a Gradle build file from Mastering Gradle by Mainak Mitra (page 70). The build script is:
println "Working on custom task in build script"
class SampleTask extends DefaultTask {
String systemName = "DefaultMachineName"
String systemGroup = "DefaultSystemGroup"
#TaskAction
def action1() {
println "System Name is "+systemName+" and group is "+systemGroup
}
#TaskAction
def action2() {
println 'Adding multiple actions for refactoring'
}
}
task hello(type: SampleTask)
hello {
systemName='MyDevelopmentMachine'
systemGroup='Development'
}
hello.doFirst {println "Executing first statement "}
hello.doLast {println "Executing last statement "}
If I run the build script with gradle -q :hello, the output is:
Executing first statement
System Name is MyDevelopmentMachine and group is Development
Adding multiple actions for refactoring
Executing last statement
As expected with the doFirst method excecuting first, the two custom actions executing in the order in which they were defined, then the doLast action executing. If I comment out the lines adding the doFirst and doLast actions, the output is:
Adding multiple actions for refactoring
System Name is MyDevelopmentMachine and group is Development
The custom actions are now executing in the reverse order in which they are defined. I'm not sure why.
I think it's simply a case that the ordering is not deterministic, and you get different results depending on how you further configure the task.
Why do you want 2 separate #TaskAction methods as opposed to a single one that calls methods in a deterministic sequence, though ? I can't think of a particular advantage of doing it that way (I realize it's from a sample given in a book).
Most other samples I find only have a single method
#TaskAction
void execute() {...}
which I think makes more sense and be more predictable.
Patrice M. is correct, the way those methods will be executed is undeterministic.
In detail
#TaskAction annotated methods are being processed by AnnotationProcessingTaskFactory.
But first Task action methods are fetched with DefaultTaskClassInfoStore and results stored in TaskClassInfo.
You can see that Class.getDeclaredMethods() is being used to fetch all methods to check if they contain #TaskAction annotation
And here the definition of public Method[] getDeclaredMethods() throws SecurityException
Description contains following note:
The elements in the returned array are not sorted and are not in any
particular order.
Link to Gradle discussion forum with the topic about #TaskAction
It doesn't guarantee the order.
For your information, just added one more link which was an issue I raised few years ago, it should be giving warnings or replaced with some other better solutions by Gradle.
https://github.com/gradle/gradle/issues/8118

Gradle variable scoping

I my root project I defined a variable and a method using it like
// An immediately executed closure (I hope)
def myvar = ({-> do something})()
def myfun() {
println myvar + ":" + project
}
and called it in a subproject. This lead to an error like
Could not find property 'myvar' on root project 'root'.
I find it strange as myvar is in scope on the function definition (so I'm probably doing something different?). Ii looks like dynamic scoping, but I can't believe it.
I know about project.ext, but I don't want to make myvar available elsewhere.
So I moved the definition into the declaration (it gets evaluated multiple times now, but who cares), but then I found out that project refers to the root project, rather than the subproject calling the function (lexical scoping). Can I get the current project without passing it explicitly?
Local variables declared at the outermost level of a script aren't in scope of methods in the same script. This is due to how Groovy translates a script into a runnable class. One way around this is to use ext.myfun = { ... } instead, which solves the scoping problem and doesn't affect call sites (which can still use myfun()).
Your other problem seems unrelated. project refers to the project that the script is associated with, and isn't determined based on the caller. It isn't possible to get the caller's project without passing it. However, it's possible to declare ext.myfun for every project, e.g. in the root script's allprojects {} block.

Resources