How do extension properties act like methods that take a closure and objects at the same time - gradle

Assume your build.gradle is very simple, like
apply plugin: 'groovy'
ext.foo1 = 'bar1'
ext {
foo2 = 'bar2'
}
assert foo1 == 'bar1'
assert foo2 == 'bar2'
This is legitimate groovy but I don't understand why. In the second reference to ext, ext is treated like a method that takes a closure which sets its owner to the instance of ext. Yet, in the first reference, it acts like just an ExtraProperties instance. Using something like:
println ext.class.name
Actually causes an error because "class" doesn't exist on ext. This might be because ext is a regular object with a dynamically added an ExtensionAware interface, which was added by extensions.create(...). But that's a farfetched, not-quite-reasonable guess.
I don't know how these kind of properties are set up. The documentation is only clear on how property extensions are intended to be used, not how they work or what they are. Can anyone explain?
(1) How does groovy know to go to project.ext.prop1 when 'prop1' is referenced in the build script?
(2) What is 'ext', really?

the following modified script should give you understanding:
apply plugin: 'groovy'
ext.foo1 = 'bar1'
ext {
println "log1:: ${it.getClass()} ${System.identityHashCode(it)}"
foo2 = 'bar2'
}
println "log2:: ${ext.getClass()} ${System.identityHashCode(ext)}"
assert foo1 == 'bar1'
assert foo2 == 'bar2'
output:
log1:: class org.gradle.api.internal.plugins.DefaultExtraPropertiesExtension 464908575
log2:: class org.gradle.api.internal.plugins.DefaultExtraPropertiesExtension 464908575
means that ext{ ... } equals to ext.with{ ... }

Related

How can I create an incremental build to derive output filenames from input filenames?

I must write a plugin to compile files of a certain type not covered by an existing plugin.
My initial requirements are simply that if the input file is changed or if the output is missing, then the task will not be up-to-date. In other words, I want the core "working with files" paradigm to work for me.
My first attempt was to declare both inputs and outputs to see if the project would work as most would expect.
class TestPlugin implements Plugin<Project> {
void apply (Project project) {
project.task('compile') {
doFirst {
inputs.files.eachWithIndex { inFilename, idx ->
def inFile = project.file(inFilename)
def outFilename = outputs.files[idx]
def outFile = project.file(outFilename)
logger.info "converting ${inFile} to ${outFile}"
outFile.append "something"
}
}
}
}
}
apply plugin: TestPlugin
compile {
inputs.file "test.in"
outputs.file "test.out"
}
And it does. At this point it does all I need except for one thing: I have to define outputs in correlation with inputs. But defining the outputs is complicated enough to warrant factoring that part of the code into the plugin's task.
My next attempt was to try to get the task to define its outputs but it fails because when the "populate outputs" code executes, inputs is empty and so no outputs are added.
class TestPlugin implements Plugin<Project> {
void apply (Project project) {
project.task('compile') {
inputs.files.each { outputs.files.add(it.replaceFirst(~/\.in$/, '.out')) }
doFirst {
inputs.files.eachWithIndex { inFilename, idx ->
def inFile = project.file(inFilename)
def outFilename = outputs.files[idx]
def outFile = project.file(outFilename)
logger.info "converting ${inFile} to ${outFile}"
outFile.append "something"
}
}
}
}
}
apply plugin: TestPlugin
compile { inputs.file "test.in" }
The above fails with a "path may not be null..." error caused by indexing an empty outputs list (because inputs is empty at the time the task's outer block iterates over inputs.
I tried populating outputs in Project.afterEvaluate and Project.beforeEvaluate but neither worked. The beforeEvaluate closure never executed. The afterEvaluate closure executed, well, after the project was evaluated which means it executed after the task was set as either up-to-date or out-of-date and so it's not useful for what I need.
I also tried Project.configure but that didn't work either.
Then I discovered lazy configuration but since it's incubating I think I should avoid it. I'm assuming that the Java and C plugins don't use incubating features and that leaves me wondering how those plugins are accomplishing the equivalent of lazy configuration.
Then I found Gradle plugin for custom language but it doesn't have an answer. It does, however, have a comment leading me to look at a lot of gradle source code. Again, I don't think I should have to reinvent the wheel.
Lastly, if I have to push the decision to compile into doFirst and not bother with declaring outputs (and thereby abandon task up-to-datedness), then so be it. I just need to know and move on.
For more context, I'm migrating a build away from ant. The two udemy.com classes I took helped me a lot but didn't go far enough to lead me in a confident direction to solve the stated problem. I asked a similar question to one of the instructors and its community to no avail.

Gradle doesn't collect strings starting with -D

Is that a Gradle or Groovy bug?
I want to pass JVM parameters from Gradle to forked JVM, which is unfortunately not done automatically. This is supposed to work, build.gradle:
...
bootRun {
jvmArgs = System.properties.iterator().findAll{it.key.startsWith('myapp')}.collect {
"-D${it.key}=${it.value}"}
}
...
It is executed as:
gradle bootRun -Dmyapp.port=34501 -Dmyapp.member.name=server1
The method collect always return empty collecting if string starts with -D. If it starts with anything else it returns expected two element String collection. If I put space before -D it also works however it breaks the build further downstream on :findMainClass misinterpreting -Dmyapp.port=... with main class name. It simply has to start with -D.
I also tried different string concatenation but as far as the result is a string starting with -D it doesn't work.
Is it a bug or I'm missing something. This is my first Gradle project and I'm not a Groovy developer.
Should I report is bug? Where, Groovy or Gradle?
Notes:
I'm running Gradle from IntelliJ IDE 2016.1.2
Using Gradle 3.5
Forked JVM runs Spring Boot application
UPDATE
Big apologies, my bad! The truth is, the JVM parameters are passed down using the formula above; the problem is with how I measured it that the weren't. I simply put printouts:
println "jvmArgs: ${jvmArgs}"
println "jvmArgs.size: ${jvmArgs.size}"
println "jvmArgs.class: ${jvmArgs.class}"
..and aborting bootRun if jvmArgs.size == 0, to avoid slow application start; that is I wasn't really checking if parameters were passed or not in the application itself. And it turned out they were.
FYI the outputs were:
jvmArgs: []
jvmArgs.size: 0
jvmArgs.class: java.lang.ArrayList
The class of jvmArgs is reported as a standard ArrayList, but behaves more like a input stream consumer, whatever array is jvmArgs assigned to, that array is scanned for all strings starting with "-D", those are consumed (by what?), passed to some ProcessBuilder (??) and jvmArgs is left only with remaining elements.
Take this example:
jvmArgs = ["-Daaa=bbb", "foo", "bar"]
jvmArgs = ["stuff", "-Dccc=ddd", "morestuff"]
jvmArgs = ["-Deee=fff"]
println "jvmArgs: ${jvmArgs}"
..it prints jvmArgs: [] and Spring Boot application is launched with -Daaa=bbb -Dccc=ddd -Deee=fff.
Can someone explain what causes this magic stream like property of jvmArgs, which otherwise claims to be a simple ListArray?
This works for me, but I don't have an explanation for the observed behavior. Hope it helps anyway.
def array = System.properties.iterator().findAll{
it.key.startsWith('myapp')
}.collect {
"-D${it.key}=${it.value}"
}
jvmArgs.addAll(array)
EDIT
jvmArgs = ["value"] calls setJvmArgs which, if I haven't missed something, goes from JavaExec to JavaExecHandleBuilder and later JvmOptions. Here, some parameters get removed. Entries beginning with -D gets added to systemproperties instead.
setJvmArgs( ["-Dtest=1", "xx"])
println getJvmArgs() //[xx]
println systemProperties //[test:1]
Does your Application does't have access to that properties?
https://github.com/gradle/gradle/blob/master/subprojects/core/src/main/java/org/gradle/process/internal/JvmOptions.java#L183
EDIT: what's happening in the background
In Groovy, a property assignment calls the setter instead, accessing it will call the getter. They are interchangeably. If you omit the setter and getter pair, it will be generated for you and will be visible in the bytecode. But you can even omit the property itself, only write the getter and setter pair and use it as a property.
class Foo {
def setBar(String foo) {println "no thanks"}
String getBar() {"test"}
}
f = new Foo()
f.bar="write Var" // println "no thanks"
println f.bar instanceof String // --> f.getBar() inst... true
println f.bar //
So you never assigned a List to a variable, but called setJvmArgs(List). You can list all args with getAllJvmArgs() btw.
In combination with delegation strategies and dynamic Properties/Methods, this can be a blessing for DSL programming, but a curse to debug...
http://groovy-lang.org/style-guide.html#_getters_and_setters
and google for groovy propertyMissing/groovy metaprogramming/groovy Resolve Strategies if you like to learn more about this topic.

Groovy instance.metaclass vs this.metaclass

I have a following script:
task myTask {}
class Person {
Person() {
Person instance = this
println this.metaClass.class.name
println this.getMetaClass().class.name
println instance.metaClass.class.name
println instance.getMetaClass().class.name
}
}
Person person = new Person()
And the output is :
groovy.lang.MetaClassImpl
groovy.lang.MetaClassImpl
org.codehaus.groovy.runtime.HandleMetaClass
org.codehaus.groovy.runtime.HandleMetaClass
Can anyone explain to me what is going on?
Thanks in advance.
Take a look at this class,
class Person {
def a, b
Person() {
a = this
b = this
println "this $this"
println "a $a"
println "b $b"
}
def printAll() {
println "this.metaClass ${this.metaClass}"
println "this.class.metaClass ${this.class.metaClass}"
println "a.metaClass ${a.metaClass}"
println "b.metaClass ${b.metaClass}"
}
}
Take a look at the screenshot of the groovysh. It might give you a little hint about what's going on.
p and q are two different objects, but
p.metaClass is the same as q.metaClass, and
printAll prints exactly the same thing for both, p and q
a.metaClass and b.metaClass are holding this.class.metaClass, not this.metaClass, you see
There is only one object created of MetaClassImpl, and also only one of HandleMetaClass, for Person. And no matter how many times you instantiate Person, they will be assigned to the instance. But, when you expand any of that instance, only then a new HandleMetaClass object will be created -- just for that particular object; and this time HandleMetaClass will be holding not MetaClassImpl, but ExpandoMetaClass instead.
See the screenshot below,
Now, to answer your question, this.metaClass is a special case, like this itself. It doesn't give you the handle, HandleMetaClass object, so you can't expand over metaClass of this -- directly; and there is no sense in that either, because then all other future instances will share that expansion.
If you really want that behaviour then you can pass this to some other variable, i.e. instance = this, as you did in the constructor, and then you can expand over that instance -- and that expansion would be true for this and all other future instances. But then, why not add the behaviour to class itself, in the first place. Why expand?

Why are the configuration styles inconsistent in gradle?

I am new to gradle and a few things of gradle confuses me. Some things appear like inconsistent coding / configuration style.
For example, when we configure the repository to be jcenter or mavencentral we call a function / method e.g. jcenter.
repositories {
jcenter()
}
However, in the same file, when we try to configure a dependency we do not call functions / methods anymore.
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
}
And then there are clearly variables getting values
productFlavors {
prod {
versionName = "1.0-paid"
}
mock {
versionName = "1.0-free"
}
}
I am sure there is a reason behind this perceived inconcistency but could not find anything when I read through the documentation. Could anybody explain the reason?
Actually these examples are not so different.
classpath 'com.android.tools.build:gradle:2.3.1'
is a function call as well. Groovy (the language in which gradle build scripts are written) allows you to leave out the parenthesis around the arguments in many cases.
This is the flexibility (I prefer this to inconsistency) that is delivered by Groovy the language that Gradle is using.
In Groovy you can call a function/method with or without the parenthesis if its name is followed by matching arguments but if there are no arguments you must add parenthesis to make it a call to a function and make it distinct from the closure it represents.
Here is an example using groovysh
groovy:000> def a(){println "a"}
===> true
groovy:000> a
===> org.codehaus.groovy.runtime.MethodClosure#95e33cc
groovy:000> a()
a
===> null
groovy:000> def b(arg){println arg}
===> true
groovy:000> b
===> org.codehaus.groovy.runtime.MethodClosure#d771cc9
groovy:000> b "argument"
argument
===> null
groovy:000> b("argument")
argument
===> null
groovy:000>

Closure defined in root not visible in child

I have root project and subproject (:child).
Root build looks like like this:
def foo = {
println("foo")
}
allprojects {
task bar << {
println(project.name + ":bar")
}
afterEvaluate {
foo()
}
}
Running gradle bar prints:
foo
foo
:bar
:child:bar
child:bar
parent:bar
This make sense. However, IRL I need foo to be called by the child's build file (because I want it to be called only by some of the submodules).
The documentation seems to be clear enough: In a multi-project build, sub-projects inherit the properties and methods of their parent project
However, moving the "afterEvaluate" block above into child/build.gradle results in an error: Could not find method foo() for arguments [] on project ':child' of type org.gradle.api.Project.
Why does this happen and how do I fix this? I have tried a whole bunch of different variations - moving the def around (to buildscript, allprojects, to ext, to allprojects.ext, making it a variable in ext, instead of a method etc.), referring to it differently (as rootProject.foo, rootProject.foo(), ext.foo() etc.) - nothing seems to work.
Any ideas?
Vars need to be declared in the ext namespace for them to be propagated downstream. Try:
ext.foo = {
println("foo")
}
ref: https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html

Resources