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.
Related
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.
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.
So I have a project where I want to run a task multiple times with a different value for a system parameter each time, however I can't seem to do that short of calling gradle multiple times in a bash script which is not desirable. I tried ./gradlew myTask -Dproperty="value1" myTask -Dproperty="value2" which ran myTask twice, which was good, but it ran with property=value2 both times. Is there any way to do this?
Edit: I should also mention that I do not know value1 and value2 until buildtime. So I can't hardcore them into the build script.
Check out the following approach:
task executeTaskTwiceWithParameters {
String[] propertyValues = System.getProperty("propertyValues" ,"").split(",")
dependsOn propertyValues.collect { "runWith$it" }
propertyValues.each { value ->
task "runWith$value"(type: GradleBuild) {
buildFile = 'build.gradle'
tasks = ['doSomething']
startParameter.systemPropertiesArgs += [property: value]
}
}
}
task doSomething {
doLast {
println System.getProperty("property")
}
}
When invoked like
./gradlew executeTaskTwiceWithParameters -DpropertyValues=value1,value2
for each property value it launches an auxiliary gradle build to execute a task with this property value set. The same trick works for project properties as well.
It will help you to think about the task as an 'instance' instead of 'type'. Then you would not have this question.
I suggest you to create another task of the same type and bind it to its arguments.
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
See the following scenario:
def dynamic = "original value"
task stuffThatHasToBeDoneBefore << {
doSomething(dynamic)
}
task b (dependsOn: stuffThatHasToBeDoneBefore) {
dynamic = "value of task b that never will be used by stuffThatHasToBeDoneBefore-task"
}
task c (dependsOn: stuffThatHasToBeDoneBefore) {
dynamic = "value of task c"
}
I want to use the stuffThatHasToBeDoneBefore-task multiple times in my build execution to reduce code duplicates.
Right now it isn't possible to execute task b and to be sure that the stuffThatHasToBeDoneBefore-task will be executed beforehand with the dynamic-value ("value of task b that never will be used by stuffThatHasToBeDoneBefore-task"), because the dynamic-variable value will be overwritten by task c in the configuration phase ("value of task c").
The only way I see to do this is the following:
task b_alternative << {
dynamic = "value of task b that will be used in stuffThatHasToBeDoneBefore-task now"
tasks.stuffThatHasToBeDoneBefore.execute()
}
Unfortunately this is just a workaround and I read in multiple sources that it isn't recommended to use tasks.taskname.execute()
Can anybody tell me how to structure the code so that I can reuse the stuffThatHasToBeDoneBefore-task with dynamic values?
Usually I would use a simple method instead of a task for it and pass the dynamic-value as an argument, but this is not possible because the stuffThatHasToBeDoneBefore-task is a Zip-typed task which is only available as a task and not as a method.
The solution to your problem is to use a custom task.
You can have your custom task extend the ZipTask, and you can define a custom dynamic property on it, and a doSometing method that uses it. You then add doSomething to doFirst or doLast depending on when you want it to execute ( before or after the zip is made ).
Your build script becomes more expressive:
// import MyCustomTask here
task b (type: MyCustomTask) {
dynamic = "value for task b"
}
task a (type: MyCustomTask) {
dynamic = "value for task a"
}
Note that dynamic is no longer a variable in your build script, it's now a property of your custom task.