What is this Groovy syntax called (Gradle 'exclude' closure syntax, e.g.)? - gradle

I feel like I've seen the term for this somewhere, but I haven't been able to find it on the web or SO. What is the name for the Groovy syntax that makes it possible to append the closure after the compile method in a Gradle dependencies closure, for example? How does it work? How would I write a function that uses this syntax?
compile ('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.slf4j', module: 'slf4j-api'
}
Thanks!

Groovy has a flexible syntax for passing a closure as the last parameter to a method. Consider:
def myCompile (a, b, c) {
println c(a,b)
}
myCompile(10, 20, { x, y -> x + y })
myCompile(10, 20) { x, y ->
x + y
}
I don't think this has a name, but more generally the Gradle build.gradle syntax forms a DSL (Domain Specific Language) which is fluid and natural. DSLs are the motivation for syntactic sugar such as this (and many other examples... it is a huge topic).

Related

what does the groovy compiler make of gradle dependsOn syntax

I understand that the gradle DSL
task doTask { logger.info "some text" }
will actually call the method task(String,Closure) on the Project delegate object. It's more or less a short hand for
task("doTaks", {logger.info("some text")})
That's all fine. But things get complicated when I try to understand some gradle DSL syntax I've seen in third party build scripts:
task doTask (dependsOn: 'otherTask'){logger.info "some text"}
I figure that groovy will create a map from (dependsOn: 'otherTask'), and that somehow the Project method
task(Map args, String name, Closure config)
will be called. But how do these additional parentheses come into play, why are they necessary, how does groovy figure out what we want here? The syntax looks totally counter-intuitive to me with my minimal groovy skill. I would never guess that I have to do it this way to make it work.
So, that's the question: how does groovy figure out what to do with this command:
task doTask (dependsOn: 'otherTask'){ // some code }
You can invoke groovy methods using one the following syntaxes:
Args must be all comma separated within parenthesis:
method(arg1, arg2, ..., argN, argClosure)
Args must be all comma separated without parenthesis:
method arg1, arg2, ..., argN, argClosure
If the last argument is a closure, all previous args must be all comma separated within parenthesis, and the code block (within braces {...}) following the closing parenthesis will be interpreted as the last Closure argument argClosure:
method(arg1, arg2, ..., argN) { ... }
If you DO NOT want the code block within the curly braces to be interpreted as a Closure argument for the preceding method invocation; then end the method invocation with a semicolon:
// Because of `;` following { ... } is not a Closure argument for `method`
method(arg1, arg2, ..., argN); { ... }
A method that accepts as an argument a single Map or a Map and a closure, may be invoked using the named parameters syntax; which will transform each named argument into an entry in the map and the last argument will be a closure. This will create the following intuitive syntactic variants:
method(name1: arg1, name2: arg2, ..., nameN: argN, argClosure)
method name1: arg1, name2: arg2, ..., nameN: argN, argClosure
method(name1: arg1, name2: arg2, ..., nameN: argN) { ... }
Groovy provides tons of syntactic sugar, so other intuitive variants can be found around this; but this gives you the feeling for groovy's way of handling Closure arguments.
The following is a working demo of these different ways of doing a method invocation:
import java.time.LocalDateTime
def handleWithClosure(data1, data2, closure) {
closure("$data1. $data2")
}
def logger = { println "${LocalDateTime.now()} - $it" }
handleWithClosure(1, 'All within parenthesis', logger)
handleWithClosure 2, 'All without parenthesis', logger
handleWithClosure(3, 'List of arguments within parenthesis and closure outside') { logger(it) }
def handleMapWithClosure(map, closure) {
handleWithClosure(map['num'], "[Named Arguments/Map based] ${map['msg']}", closure)
}
handleMapWithClosure(msg: 'All within parenthesis', num: 1, logger)
handleMapWithClosure msg: 'All without parenthesis', num: 2, logger
handleMapWithClosure(msg: 'List of arguments within parenthesis and closure outside', num: 3) { logger(it) }
Running it you can see how it is treating all these syntactic options.
Complete code on GitHub
Hope this helps.

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.

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>

Gradle looping inside ProcessResources and filter

In a jar task, I want to replace some texts in a conf file.
jar {
ext {
excludedClasses = ["com.MyClass1", "com.MyClass2"]
}
doFirst {
println 'Jar task started execution'
println 'Excluded classes ' + excludedClasses
exclude(excludedClasses)
}
doLast {
println 'Jar task finished execution'
}
processResources {
filesMatching('**/moduleconfiguration/conf.json') { f ->
excludedClasses.each { c ->
filter {
println it
it.replace(c, "com.MyClass3")
}
}
}
}
}
But the above code tries to replace c from all *.class files, resulting in an illegal jar. I want it to make replacements only in '**/moduleconfiguration/conf.json' file.
How can I achieve that?
UPDATE
Looks like I am suffering from the same problem happening here: https://issues.gradle.org/browse/GRADLE-1566. This issue has already been resolved but reoccurs if I use an each loop inside processResources.
Meanwhile, I have found 2 solutions to my problem as follows:
Solution 1: Changing order of filter and each loop. i.e. Looping inside filter
filesMatching('**/moduleconfiguration/conf.json') { f ->
filter {
excludedClasses.each { c ->
println it
it = it.replace(c, "com.MyClass3")
}
it
}
}
Solution 2: Using regex instead of each loop
filesMatching('**/moduleconfiguration/conf.json') { f ->
filter {
println it
def regex = excludedClasses.join("|") // watch for .(dot) or other regex chars here
it.replaceAll(regex, "com.MyClass3")
}
}
I am still wondering why the scope of filtering changes to all files if I use each loop within the filesMatching method closure. Is this a groovy thing or gradle thing? I would be very thankful if someone could explain what is happening there.
UPDATE 2
println output of values of delegate, this and owner at different positions for problematic case:
:processResources
Inside filesMatching. delegate:file '.../configuration/conf.json' this:root project 'projectName' owner:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90#6f3e18b8
Problematic Case inside loop before filter. delegate:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91#4587ec31 this:root project 'projectName' owner:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91#4587ec31
Problematic Case inside loop before filter. delegate:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91#4587ec31 this:root project 'projectName' owner:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91#4587ec31
:classes
:jar
Jar task started execution
Excluded classes [MyClass1.class, MyClass2.class]
Problematic Case inside loop inside filter. delegate:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91_closure92#3a0d0128 this:root project 'projectName' owner:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91_closure92#3a0d0128
.
.
.
.
.
println output of values of delegate, this and owner at different positions for solution 1:
:processResources
Inside filesMatching. delegate:file '.../configuration/conf.json' this:root project 'projectName' owner:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90#6ece61a3
Solution 1 Inside filter before loop. delegate:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91#64af2ad7 this:root project 'projectName' owner:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91#64af2ad7
Solution 1 Inside filter inside loop. delegate:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91_closure92#22c74276 this:root project 'projectName' owner:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91_closure92#22c74276
Solution 1 Inside filter inside loop. delegate:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91_closure92#22c74276 this:root project 'projectName' owner:build_95q5jrf5z5ao0hk03tsevn2t0$_run_closure10_closure90_closure91_closure92#22c74276
.
.
.
.
.
:classes
:jar
Jar task started execution
Excluded classes [MyClass1.class, MyClass2.class]
Update
Based on your second update and some testing on my side, it doesn't seem to directly be what I originally suggested. It does definitely appear to be something with delegation, but I can't pin it down.
You can illustrate the difference in your original (problematic) example by changing the filter { line to f.filter {. This explicitly tries to execute the FileCopyDetails#filter method instead of whichever happens to be in scope.
You should be able to see that when calling f.filter only the matched file is filtered. When you call filter on its own, it is calling the task level one. However, since you are already in the middle of copying files, it only applies the filter to files that occur alphabetically after the first one that matches.
For example, if you have this folder structure:
+ resources/main
- abc.json
- configuration.json
- def.json
- efg.json
The def.json and efg.json will be filtered, but the first two will not.
This still doesn't answer why it doesn't call the correct filter method, but it at least confirms that it is calling the task level one.
Original Answer
I believe this is due to the Groovy closures delegating to different objects. I believe you are actually calling Copy#filter in the original (problematic) case, and FileCopyDetails#filter in the two solutions. The copy task's filter method will apply a filter to everything, whereas the details filter method will be for the specific file in your filesMatching.
You should be able to illustrate the different delegates by printing them out:
Original
processResources {
filesMatching('**/moduleconfiguration/conf.json') { f ->
excludedClasses.each { c ->
// print the class of this closure's delegate
println delegate.class
filter {
it.replace(c, "com.MyClass3")
}
}
}
}
Solution 1
filesMatching('**/moduleconfiguration/conf.json') { f ->
filter {
// print the class of this closure's delegate
println delegate.class
excludedClasses.each { c ->
println it
it = it.replace(c, "com.MyClass3")
}
it
}
}
My expectation is that you will see the original delegating to your Copy task (i.e. processResources) and the solutions delegating to the FileCopyDetails. By default all closures in Groovy are "owned" by the object in which they were declared, and also will "delegate" to the owner. When Gradle has API's that take a closure as an argument (such as the filter methods), they are usually reconfigured to have a specific delegate (usually the enclosing container object) and to use the "delegate first" strategy to lookup methods.
See Groovy's documentation on delegation strategies.

Why did Playframework 2 use a custom Scala Template Engine instead of scalas build in xml mode?

Play 2.0 uses a custom scala based template engine that allows to use a subset of scala inside html code.
Why was this design decision made instead of using scalas build-in xml mode?
The play template engine has some disadvantages like
only a subset of scala is supported, for example it seems not to be possible to define functions inside of functions
no editor support in eclipse
On the other hand, I understand that the play scala template engine supports non-well-formed html which wouldn't be possible with scalas xml mode, but I guess it should always be possible to write the templates in a well formed way. I'm just a beginner in play and scala and would simply like to understand the context.
I think it has several answers :
It is a template. Templates are not supposed to hold complex logic. All logic manipulation has to be done in the controller/model.
Templates can have about any format you want : email, CSV, SQL and so on. Restricting a template to valid XML is really limiting the possibilities of the framework.
Everything is compilable. Even routes, assets, template inheritance, etc. In order for those mecanisms to work with the rest of the framework, some choice probably had to be made. But no one will be able to answer you better than the creators of the framework.
One reason may be the clumsy handling of attributes in xml in scala. There are two problems:
Attributes like "selected" which must either be present or not
Adding a list of attributes dynamically like the htmlArgs of the input helper templates
An example in plain scala shows the difficulties:
def addAttributes(element: Elem, attributes: Map[Symbol, _ <: Any]): Elem = {
var el = element
for ((key, value) <- attributes) el = el % Attribute(None, key.name, Text(value.toString), Null)
el
}
def multiselect[T](field: play.api.data.Field,
options: Seq[T],
optionValue: T => String,
optionText: T => String,
args: (Symbol, Any)*)(implicit handler: FieldConstructor, lang: play.api.i18n.Lang) = {
val values = { field.indexes.map { v => field("[" + v + "]").value } }
input(field, args: _*) {
(id, name, value, htmlArgs) =>
Html(
addAttributes(
<select id={ id } name={ name } multiple="multiple">
{
options.map { v =>
val z: Option[String] = if (values contains v) Some("selected") else None
<option value={ optionValue(v) } selected={ z map Text }>{ optionText(v) }</option>
}
}
</select>,
htmlArgs).toString)
}
}

Resources