-
Bug
-
Resolution: Unresolved
-
Major
-
None
-
Jenkins 2.32.1
pipeline plugin versions:
Pipeline 2.5
Pipeline Graph Analysis Plugin 1.2
Pipeline REST API for Blue Ocean 1.0.0-b15
Pipeline: API 2.17
Pipeline: Basic Steps 2.5
Pipeline: Build Step 2.5
Pipeline: Classpath Steps 0.1.0
Pipeline: Declarative Agent API 1.1.1
Pipeline: Declarative Extension Points API 1.1.6
Pipeline: Groovy 2.34
Pipeline: Input Step 2.7
Pipeline: Job 2.11
Pipeline: Milestone Step 1.3.1
Pipeline: Model API 1.1.6
Pipeline: Model Definition 1.1.6
Pipeline: Multibranch 2.15
Pipeline: Nodes and Processes 2.11
Pipeline: REST API Plugin 2.8
Pipeline: SCM Step 2.4
Pipeline: Shared Groovy Libraries 2.8
Pipeline: Stage Step 2.2
Pipeline: Stage Tags Metadata 1.1.6
Pipeline: Stage View Plugin 2.8
Pipeline: Step API 2.11
Pipeline: Supporting APIs 2.14Jenkins 2.32.1 pipeline plugin versions: Pipeline 2.5 Pipeline Graph Analysis Plugin 1.2 Pipeline REST API for Blue Ocean 1.0.0-b15 Pipeline: API 2.17 Pipeline: Basic Steps 2.5 Pipeline: Build Step 2.5 Pipeline: Classpath Steps 0.1.0 Pipeline: Declarative Agent API 1.1.1 Pipeline: Declarative Extension Points API 1.1.6 Pipeline: Groovy 2.34 Pipeline: Input Step 2.7 Pipeline: Job 2.11 Pipeline: Milestone Step 1.3.1 Pipeline: Model API 1.1.6 Pipeline: Model Definition 1.1.6 Pipeline: Multibranch 2.15 Pipeline: Nodes and Processes 2.11 Pipeline: REST API Plugin 2.8 Pipeline: SCM Step 2.4 Pipeline: Shared Groovy Libraries 2.8 Pipeline: Stage Step 2.2 Pipeline: Stage Tags Metadata 1.1.6 Pipeline: Stage View Plugin 2.8 Pipeline: Step API 2.11 Pipeline: Supporting APIs 2.14
Hi,
First of all - I will describe what I'm trying to do:
I have multiple "library" pipeline scripts. I'm using the pipeline Load step to load them.
Some scripts need to use methods in other scripts - think of the following scenario:
pipeline Job loads script1.groovy and script2.groovy , script1.groovy loads script2.groovy as well. This results in script2.groovy being loaded twice for no good reason.
I want to achieve lazy loading, meaning if the same script has already been loaded - I want to reuse it and not load it again, meaning script1.groovy should use script2.groovy which was loaded by the job itself.
I tried to do it with the following code - in this example I do want the 2nd script to load the file on its own:
job code:
timestamps { node('some node') { //some code to get the files to the workspace - not relevant to the situation def test = load("./src/jenkins/pipeline/utils/test.groovy") test.test() } }
test.groovy
/* ***** lazy loading - BEGIN ***** */ this._scripts = [:] def getScript(def scriptName) { // lazy loading if (this._scripts."${scriptName}" == null) { this._scripts."${scriptName}" = load("${WORKSPACE}/src/jenkins/pipeline/utils/${scriptName}.groovy") this._scripts."${scriptName}".init(this._scripts) } return this._scripts."${scriptName}" } def init(def scripts) { if (scripts) this._scripts = scripts } /* ***** lazy loading - END ***** */ def test() { getScript("command_runner").runHostCommand("echo 'hello world'") } return this
command_runner.groovy
/* ***** lazy loading - BEGIN ***** */ this._scripts = [:] def getScript(def scriptName) { // lazy loading if (this._scripts."${scriptName}" == null) { this._scripts."${scriptName}" = load("${WORKSPACE}/src/jenkins/pipeline/utils/${scriptName}.groovy") this._scripts."${scriptName}".init(this._scripts) } return this._scripts."${scriptName}" } def init(def scripts) { if (scripts) this._scripts = scripts } /* ***** lazy loading - END ***** */ /** * Runs a command and returns the exit code. assumes windows [for now] * @param command * @return */ def runHostCommand(def command) { int exitCode exitCode = bat script: command, returnStatus: true return exitCode } def runHostCommandWithOutput(def command) { currentDir = pwd() println "${currentDir}>${command}" def output = bat (script: command, returnStdout: true).trim() //output contains the call the command itself so we should remove it when printing output = output.substring(output.indexOf('\n')+1); println output return output } /* *** mandatory to be able to call from another bootstrap script *** */ return this
When running it - I get the following exception
... [Pipeline] load [Pipeline] { (./src/jenkins/pipeline/utils/test.groovy) [Pipeline] } [Pipeline] // load [Pipeline] load [Pipeline] { (F:\jenkins-remote\workspace\Mor\pipeline_tests/src/jenkins/pipeline/utils/command_runner.groovy) [Pipeline] } [Pipeline] // load [Pipeline] } [Pipeline] // node [Pipeline] } [Pipeline] // timestamps [Pipeline] End of Pipeline java.lang.NullPointerException: Cannot invoke method init() on null object at org.codehaus.groovy.runtime.NullObject.invokeMethod(NullObject.java:91) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:48) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at org.codehaus.groovy.runtime.callsite.NullCallSite.call(NullCallSite.java:35) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.methodCall(DefaultInvoker.java:18) at Script1.getScript(Script1.groovy:13) at Script1.test(Script1.groovy:30) at WorkflowScript.run(WorkflowScript:26) at ___cps.transform___(Native Method) at com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:57) at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:109) at com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixArg(FunctionCallBlock.java:82) at sun.reflect.GeneratedMethodAccessor270.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72) at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.get(PropertyishBlock.java:76) at com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30) at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.fixName(PropertyishBlock.java:66) at sun.reflect.GeneratedMethodAccessor355.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72) at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21) at com.cloudbees.groovy.cps.Next.step(Next.java:83) at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:173) at com.cloudbees.groovy.cps.Continuable$1.call(Continuable.java:162) at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:122) at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:261) at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:162) at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:174) at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:330) at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$100(CpsThreadGroup.java:82) at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:242) at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:230) at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:64) at java.util.concurrent.FutureTask.run(Unknown Source) at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:112) at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28) at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.util.concurrent.FutureTask.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source) Finished: FAILURE
According to the log, the Load command within test.groovy succeeded, but for some reason returned null.
so I'm looking for either a solution to the problem, or a better way to achieve lazy loading. And yes - I am familiar with Shared Pipeline Libraries plugin which is not suitable for my case at this stage.
Thanks in advance,
Mor
It looks as though if I replace the groovy property notation
with standard map put/get calls - it works (at least it doesn't fail with the same error for the exact same scenario - I haven't tested all scenarios yet)
So this must be a pipeline bug...
If it works for all scenarios - then I have my lazy loading working which is great