6

I'm trying to read properties from my pom.xml file. I tried the following and it worked:

steps {
    script {
        def xmlfile = readFile "pom.xml"
        def xml = new XmlSlurper().parseText(xmlfile)
        def version = "${xml.version}"
        echo version
    }
}

When I tried to do something like this:

steps {
    script {
        def xmlfile = readFile "pom.xml"
        def xml = new XmlSlurper().parseText(xmlfile)
        def version = "${xml.version}"
        def mystring = "blabhalbhab-${version}"
        echo mystring
    }
}

the pipeline suddenly fails with the error:

Caused: java.io.NotSerializableException: groovy.util.slurpersupport.NodeChild

What might be the problem here?

EDIT: just adding this for others finding it with the same use case. My specific question was about how to avoid the CPS related error with XmlSlurper(). BUT for anyone else trying to parse POMs, afraid of that PR that supposedly will deprecate readMavenPom, the safest most maveny way of doing this is probably something like:

def version = sh script: "mvn help:evaluate -f 'pom.xml' -Dexpression=project.version -q -DforceStdout", returnStdout: true trim()

This way your using maven itself to tell you what the version is and not grepping or sedding all over the damn place. How to get Maven project version to the bash command line

red888
  • 27,709
  • 55
  • 204
  • 392

1 Answers1

5

In general, using groovy.util.slurpersupport.NodeChild (the type of your xml variable) or groovy.util.slurpersupport.NodeChildren (the type of xml.version) inside CPS pipeline is a bad idea. Both classes are not serializable, so you can't predicate their behavior in the Groovy CPS. For instance, I run successfully your second example in my Jenkins Pipeline. Most probably because the example you gave is not complete or something like that.

groovy:000> xml = new XmlSlurper().parseText("<tag></tag>")
===> 
groovy:000> xml instanceof Serializable
===> false
groovy:000> xml.tag instanceof Serializable
===> false
groovy:000> xml.dump()
===> <groovy.util.slurpersupport.NodeChild@0 node=groovy.util.slurpersupport.Node@5b1f29fa parent= name=tag namespacePrefix=* namespaceMap=[xml:http://www.w3.org/XML/1998/namespace] namespaceTagHints=[xml:http://www.w3.org/XML/1998/namespace]>
groovy:000> xml.tag.dump()
===> <groovy.util.slurpersupport.NodeChildren@0 size=-1 parent= name=tag namespacePrefix=* namespaceMap=[xml:http://www.w3.org/XML/1998/namespace] namespaceTagHints=[xml:http://www.w3.org/XML/1998/namespace]>
groovy:000> 

If you want to read pom.xml file, use the readMavenPom pipeline step. It is dedicated to read pom files and what is most important - it is safe to do it without applying any workarounds. This step comes with the pipeline-utility-steps plugin.

However, if you want to use XmlSlurper for some reason, you need to use it inside the method that is annotated with @NonCPS. That way you can access "pure" Groovy and avoid problems you have faced. (Yet still using readMavenPom is the safest way to achieve what you are trying to do.) The point here is to use any non-serializable objects inside a @NonCPS scope so the pipeline does not try to serialize it.

Below you can find a simple example of the pipeline that shows both approaches.

pipeline {
    agent any 

    stages {
        stage("Using readMavenPom") {
            steps {
                script {
                    def xmlfile = readMavenPom file: "pom.xml"
                    def version = xmlfile.version
                    echo "version = ${version}"
                }
            }
        }

        stage("Using XmlSlurper") {
            steps {
                script {
                    def xmlfile = readFile "pom.xml"
                    def version = extractFromXml(xmlfile) { xml -> xml.version }
                    echo "version = ${version}"
                }
            }
        }
    }
}

@NonCPS
String extractFromXml(String xml, Closure closure) {
    def node = new XmlSlurper().parseText(xml)
    return closure.call(node)?.text()
}

PS: not to mention that using XmlSlurper requires at least script 3 approvals before you can start using it.

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • Thanks! Btw everyone seems to forget to mention readMavenPom is being deprecated- so no one should be using it right? Also should I define this noncps function in src or should I put it in vars instead? So it's reusable in multiple pipelines – red888 Nov 09 '19 at 22:12
  • 1
    Thanks for bringing attention to that, I never heard such rumors. I guess you refer to this - https://github.com/jenkinsci/pipeline-utility-steps-plugin/pull/47 According to pipeline-utility-steps-plugin maintainers, `readMavenPom` is far from being deprecated. There are some concerns mentioned by the author of that pull request, but let's be serious - calling `mvn` to extract e.g. `project.version` doesn't sound like a valid alternative. I use `readMavenPom` step for a very long time on non-master nodes and I never had any problems with that step. – Szymon Stepniak Nov 10 '19 at 18:42
  • Im still curious where this should go. If i write a function like this id want to tuck it away. Would it be better to create it as a custom build step in vars or treat it like a library in src. for something like this that is just text processing I would probably put it in src but I dont see many best practices around this – red888 Nov 22 '19 at 15:17