Reputation: 4572
I'm building a scripted Jenkinsfile (written in Groovy, which I do not know all that well -- nor do I know Java for that matter)
I have a particular string variable that for some reason doesn't want to behave properly and I'm perplexed.
import groovy.transform.Field
// THIS WORKS
@Field def FOO = "Mary had a little lamb"
@Field def BAR = "whose fleece was white as snow"
@Field def FOOBAR = "${FOO} ${BAR}"
echo "FOOBAR: ${FOOBAR}"
echo "FOO & BAR: ${FOO} ${BAR}"
results as you would expect
[Pipeline] echo
FOOBAR: Mary had a little lamb whose fleece was white as snow
[Pipeline] echo
FOO & BAR: Mary had a little lamb whose fleece was white as snow
However, the following:
// THIS DOES NOT WORK AS EXPECTED
@Field def BAR = "and she murdered it"
@Field def FOO = "Mary had a little lamb"
if (FOO) {
BAR = "whose fleece was white as snow"
}
@Field def FOOBAR = "${FOO} ${BAR}"
echo "FOOBAR: ${FOOBAR}"
echo "FOO & BAR: ${FOO} ${BAR}"
results
[Pipeline] echo
FOOBAR: Mary had a little lamb and she murdered it
[Pipeline] echo
FOO & BAR: Mary had a little lamb whose fleece was white as snow
Is there some nuance to order of events in Java/Groovy that I'm not understanding? (I'm a python guy)
BAR
is overwritten but it would appear when defining the variable FOOBAR
, it uses the original value for BAR
whereas when simply building the string in the echo statement it uses the new value for BAR
. What?!
Or is this a nuance with how Jenkins is working in conjunction with Groovy?
Upvotes: 0
Views: 473
Reputation: 171084
When you write a Groovy script, groovy effectively wraps your script in a standard java class, inside a method run()
So if we remove the @Field
annotations, the script:
def FOO = "Mary had a little lamb"
def BAR = "whose fleece was white as snow"
def FOOBAR = "${FOO} ${BAR}"
echo "FOOBAR: ${FOOBAR}"
echo "FOO & BAR: ${FOO} ${BAR}"
Becomes effectively:
public class script1582564680906 extends groovy.lang.Script {
public java.lang.Object run() {
java.lang.Object FOO = 'Mary had a little lamb'
java.lang.Object BAR = 'whose fleece was white as snow'
java.lang.Object FOOBAR = "$FOO $BAR"
this.echo("FOOBAR: $FOOBAR")
this.echo("FOO & BAR: $FOO $BAR")
}
}
Plus some other stuff that isn't important for this question... This is fine, but if you add a method to your script, like so:
def something() {
FOO = 'tim'
}
def FOO = "Mary had a little lamb"
def BAR = "whose fleece was white as snow"
def FOOBAR = "${FOO} ${BAR}"
echo "FOOBAR: ${FOOBAR}"
echo "FOO & BAR: ${FOO} ${BAR}"
The class becomes:
public class script1582564855552 extends groovy.lang.Script {
public java.lang.Object run() {
java.lang.Object FOO = 'Mary had a little lamb'
java.lang.Object BAR = 'whose fleece was white as snow'
java.lang.Object FOOBAR = "$FOO $BAR"
this.echo("FOOBAR: $FOOBAR")
this.echo("FOO & BAR: $FOO $BAR")
}
public java.lang.Object something() {
FOO = 'tim'
}
}
As you can see, in my something()
method, I'm trying to access FOO
, but this is only valid inside the run()
method.
This is why the @Field
annotation exists. It tells Groovy to move the definition up to class level instead of inside run()
, so this:
import groovy.transform.Field
@Field def FOO = "Mary had a little lamb"
@Field def BAR = "whose fleece was white as snow"
@Field def FOOBAR = "${FOO} ${BAR}"
echo "FOOBAR: ${FOOBAR}"
echo "FOO & BAR: ${FOO} ${BAR}"
Becomes this:
public class script1582565001609 extends groovy.lang.Script {
java.lang.Object FOO = 'Mary had a little lamb'
java.lang.Object BAR = 'whose fleece was white as snow'
java.lang.Object FOOBAR = "$FOO $BAR"
public java.lang.Object run() {
this.echo("FOOBAR: $FOOBAR")
this.echo("FOO & BAR: $FOO $BAR")
}
}
So now we get to your question... Taking this:
@Field def BAR = "and she murdered it"
@Field def FOO = "Mary had a little lamb"
if (FOO) {
BAR = "whose fleece was white as snow"
}
@Field def FOOBAR = "${FOO} ${BAR}"
echo "FOOBAR: ${FOOBAR}"
echo "FOO & BAR: ${FOO} ${BAR}"
And converting it following the above rules gives us:
public class script1582565140864 extends groovy.lang.Script {
java.lang.Object BAR = 'and she murdered it'
java.lang.Object FOO = 'Mary had a little lamb'
java.lang.Object FOOBAR = "$FOO $BAR"
public java.lang.Object run() {
if ( FOO ) {
BAR = 'whose fleece was white as snow'
}
this.echo("FOOBAR: $FOOBAR")
return this.echo("FOO & BAR: $FOO $BAR")
}
}
As you can see... FOOBAR
is set up as a field, so it initialised way before you modify BAR
to be 'whose fleece was white as snow'
But FOO and BAR are as you'd expect when you debug them...
Avoid @Field
if you can get away with it, as it makes things hard to reason about
Upvotes: 3