Marcel Wilson
Marcel Wilson

Reputation: 4572

String variable assignment does not include string reference in Jenkinsfile

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

Answers (1)

tim_yates
tim_yates

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

Related Questions