0

I have this code in vars/mavenBuildSpike.groovy:

@NonCPS
def createSqBuilder(SqBuildConfig config) {
    System.out.println("createSqBuilder=${config}")
    // The constructor contains code which the CPS transformer can't handle.
    def result = new SqBuilder(config)
    System.out.println("result=${result}")
    return result
}

def call(Closure body) {

    echo 'Creating ConfigBuilderWrapper'
    def wrapper = new ConfigBuilderWrapper()
    echo 'Calling apply()'
    wrapper.apply(body)
    echo 'Done processing closure'

    def config = wrapper.builder.build()
    echo "config=${config.dump()}"

    echo 'Creating builder'
    def builder = createSqBuilder(config)    // <<--- This doesn't work.
    echo "builder=${builder}"

    echo builder.dump()
    ...

The output is:

...everything looks good...
[Pipeline] echo
Creating builder
[Pipeline] echo
builder=null
[Pipeline] End of Pipeline
java.lang.NullPointerException: Cannot invoke method hashCode() on null object
    at org.codehaus.groovy.runtime.NullObject.hashCode(NullObject.java:174)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.dump(DefaultGroovyMethods.java:291)
    ...
    at org.kohsuke.groovy.sandbox.impl.Checker.checkedCall(Checker.java:159)
    at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:17)
    at mavenBuildSpike.call(...\branches\master\builds\16\libs\sq-pipeline-library-spike\vars\mavenBuildSpike.groovy:33)
    at WorkflowScript.run(WorkflowScript:4)
    at ___cps.transform___(Native Method)
    ....

That is, the method createSqBuilder is never called and just replaced with an assignment: def builder = new NullObject().

Why is that and how can I fix it?

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • While I am not completely sure what is wrong about your syntax/usage, in general those kind of utility/helper methods that are invoked from the global var in a shared library are defined inside src within a package and then imported in a global var for usage. That definitely works. – Matthew Schuchard Feb 21 '19 at 16:19
  • @MattSchuchard How do I define a global variable in `vars/mavenBuildSpike.groovy`? And how "global" is it? Shared between all Jenkins jobs or just global for the single job? – Aaron Digulla Feb 22 '19 at 09:08
  • In Jenkins Pipeline, in general everything defined within `vars/*.groovy` in a shared library is a global var. – Matthew Schuchard Feb 22 '19 at 11:34

1 Answers1

1

Before running the code, Jenkins will do an AST transformation called "CPS transformation". This transformer doesn't support everything that Groovy can do and it won't tell you when it can't - you'll just get weird or useless error messages running the resulting code and sometimes no errors at all - the build will simply fail without any error message or stack trace anywhere.

It seems that the CPS transform doesn't like calling constructors with arguments. This worked for me:

@Field // groovy.transform.Field
SqBuilder builder = new SqBuilder()

def call(Closure body) {
    ...
    //def builder = createSqBuilder(config)  // Doesn't work!!!
    builder.init(config) // This works; move the code from the constructor to the init() method.
    ...

The @Field annotation is necessary to turn the local variable builder into a field of the class which Groovy will create at runtime. The name of this class is WorkflowScript.

You could also just type builder = new SqBuilder() (without type or def before the variable name). But that would put builder into the pool of global variables (called "Binding" in Groovy). Jenkins puts it's own stuff there (like env or scm) so that could cause strange problems when you install more plugins.

See also: Strange variable scoping behavior in Jenkinsfile

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820