2

How can I define a class in a Jenkins shared library that contains an empty JSON object?

// src/org/build/Report.groovy

package org.build

public class Report implements Serializable {
  def steps
  def json

  Report(steps) {
    this.steps = steps

    this.json = emptyJson()
  }

  @NonCPS
  def emptyJson() {
    return this.steps.readJSON( text: '{}' )
  }
}

...is instantiated from this pipeline:

@Library('my-library')
import org.build.Report
pipeline {
  agent any
  stages {
    stage("foo") {
      steps {
        script {
          rep = new org.build.Report(this)
        }
      }
    }
  }
}

...and fails with the error: expected to call org.build.Report.<init> but wound up catching readJSON; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/


I had only earlier today thought I'd figured out how to solve this "class" of problem.
Earlier, I encountered the same error when invoking a shared-library function from a shared-library class. I fixed that problem per the guidance at the link that the error message noted, i.e. annotating the shared-library function with @NonCPS.
I.e. in the code below, class FirstClass is able to invoke function firstNonNull() because the function is annotated with @NonCPS; without the annotation, this code generated the same error as in the question above:

// src/org/example/FirstClass.groovy

package org.example

public class FirstClass implements Serializable {
  def steps
  def var

  FirstClass(steps) {
    this.steps = steps
    this.var = steps.utils.firstNonNull( [null, null, "assigned_from_ctor"] )
  }
}
// vars/utils.groovy

@NonCPS
def firstNonNull( arr ) {
  for ( def i in arr ) { if ( i ) { return i } }

  return null
}
@Library('my-library')
import org.example.FirstClass
pipeline {
  agent any
  stages {
    stage("foo") {
      steps {
        script {
          first_class = new org.example.FirstClass(this)
        }
      }
    }
  }
}

Why does this approach not work with the Report class invoking readJSON?

StoneThrow
  • 5,314
  • 4
  • 44
  • 86
  • 2
    To my understanding, you can't call jenkins step inside @NonCPS. And readJson is a pipeline step. Btw, you could define emptyJson() as `def emptyJson(){ [:] }` – daggett Jun 30 '21 at 06:22
  • Try to warp the initiation of `FirstClass` inside a groovy file in your vars folder, then from the folder use the initiation function from the groovy file instead of calling the class directly. – Noam Helmer Jun 30 '21 at 09:35
  • 1
    Agree with @daggett on both counts. It does look more like you want an empty Map than an empty JSON, which would be `{}`, so the suggestion would clear the issue for you. – Matthew Schuchard Jun 30 '21 at 12:30

0 Answers0