63

Is there any way a Jenkins build can be aware of the Maven version number of a project after processing the POM?

I've got some projects where versioning is controlled by Maven, and in a post-build job we'd like to create a Debian package and call some shell scripts. What I need is for the version number that Maven used to be available as a Jenkins environment variable so I can pass it to post-build actions.

To be clear, I'm not needing to know how to get Jenkins to pass a version number to Maven; instead I want Maven to pass a version number to Jenkins!

Raedwald
  • 46,613
  • 43
  • 151
  • 237
DeejUK
  • 12,891
  • 19
  • 89
  • 169

11 Answers11

76

You can use the ${POM_VERSION} variable, which was introduced with https://issues.jenkins-ci.org/browse/JENKINS-18272

Nils Breunese
  • 1,169
  • 2
  • 8
  • 12
  • Awesome! Verify you need Jenkins maven plugin v2.3 or later for ` ${POM_VERSION}` to work and I think it needs to be a Maven project type. – Steve Jansen May 13 '14 at 12:55
  • 1
    This didn't work for me. Maven Interation plugin in Jenkins is version 2.5, still it says: ${ENV, var="POM_VERSION"}: bad substitution if I try to use this variable directly or via the above mentioned way. Unrecognized macro 'POM_VERSION' in '${POM_VERSION}.${BUILD_NUMBER}' during Build Number Set plugin box. – AKS Jul 30 '14 at 20:46
  • 2
    Keep in mind that POM_VERSION does not take into account any environment variables that may have been passed to the maven build process. For example: ${CUSTOM_VERSION} will be empty or with default value. – swbandit Feb 04 '15 at 18:13
  • 2
    POM_VERSION is cached incorrectly due to this bug https://issues.jenkins-ci.org/browse/JENKINS-24869 – yegeniy Oct 31 '16 at 14:19
23

After a lot of digging around (I never realised how poorly-documented Jenkins is!) I found a quite trivial solution.

  1. Install the Groovy plugin
  2. Add a Post Step to your Maven build of type Execute **system** Groovy script
  3. Paste in the following snippet of Groovy:

Script:

import hudson.model.*;
import hudson.util.*;

def thr = Thread.currentThread();
def currentBuild = thr?.executable;
def mavenVer = currentBuild.getParent().getModules().toArray()[0].getVersion();
def newParamAction = new hudson.model.ParametersAction(new hudson.model.StringParameterValue("MAVEN_VERSION", mavenVer));
currentBuild.addAction(newParamAction);

The build environment variable called MAVEN_VERSION will now be available for substitution into other post-build steps in the usual manner (${MAVEN_VERSION}). I'm using it for Git tagging amongst other things.

Bombe
  • 81,643
  • 20
  • 123
  • 127
DeejUK
  • 12,891
  • 19
  • 89
  • 169
  • 2
    This works like a charm, if you update the `def mavenVer = ` line to what's in your [later followup](http://stackoverflow.com/a/10205702/487663) - `def mavenVer = currentBuild.getParent().getModules().toArray()[0].getVersion();` – Michael K Sep 26 '12 at 13:13
  • Works indeed when "def mavenVer..." is replaced with the code from the previous comment. – Jan Goyvaerts Oct 29 '12 at 15:25
  • 1
    This has the same issue as `POM_VERSION`, due to this bug issues.jenkins-ci.org/browse/JENKINS-24869 – yegeniy Oct 31 '16 at 14:35
  • 1
    This works only with SYSTEM Groovy Scripts to have access to Jenkins' JVM objects (like build execution and parameters action). However, executing in SYSTEM has one drawback : it will execute on master, meaning that if your build is executed on a slave, pom.xml won't be found since workspace resides on the slave and is not accessible from the master. – Frédéric Camblor Feb 24 '17 at 15:26
11

As other answers already pointed out, if you are using the Maven project type, you have access to the $POM_VERSION variable. But if you are not, you can use this sequence of steps (ugly but reliable). Doing it this way relies on the same version of maven to determine the pom version (while handling complex parent/child pom inheritance where <version> may not even be present for the child).

  1. Maven step with this goal:

    org.apache.maven.plugins:maven-help-plugin:2.1.1:evaluate -Dexpression=project.version -l version.log

  2. Shell step: (You may need to adjust the path to version.log depending on your hierarchy)

    echo "POM_VERSION=$(grep -v '\[' version.log)" > props.properties

  3. Inject Environment Variables step (Environment Injector Plugin):

    Properties File Path: props.properties

Now you can use $POM_VERSION as if this were a Maven project.

What this does: Uses maven to print out the version together with a mess of output, then greps out the mess of output leaving just the version, writes it to a file using properties file format and then injects it into the build environment. The reason this is better than a one-liner like mvn ..... | grep -v '\[' is that using a Maven step does not make assumptions about the installed maven versions and will be handled by the same auto-installation as any other maven steps.

Community
  • 1
  • 1
Akom
  • 1,484
  • 19
  • 25
  • 2
    One problem with the grep filter there: if you don't have the plugins in repository, you'll get also the "Downloading..." lines in POM_VERSION. E.g.: `+ echo 'POM_VERSION=Downloading: ...` – apa64 Nov 22 '17 at 08:23
  • 1
    `echo "POM_VERSION=$(xmllint --xpath '/*[local-name()="project"]/*[local-name()="version"]/text()' pom.xml)" > props.properties` is an alternative way to get the POM. It's not portable, relies on xmllint. – rince Jul 26 '19 at 18:51
9

I used Pipeline Utility Steps plugin in a declarative pipeline job to get Maven version. In the example below I use script variable instead of environment variable, because that can be modified and passed between stages.

def TAG_SELECTOR = "UNINTIALIZED"
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh "mvn --batch-mode -U deploy"
                script {
                    TAG_SELECTOR = readMavenPom().getVersion()
                }
                echo("TAG_SELECTOR=${TAG_SELECTOR}")
            }
        }
    }
}

Note: You must approve the getVersion() method after creating the job in Manage jenkins > In-process Script Approval.

See also:

apa64
  • 1,578
  • 1
  • 17
  • 26
  • 1
    Super simple, elegant and reliable! (Note that you don't need to use the full syntax `getVersion()` in Groovy, you can instead use simply `.version`.) – Lii Oct 15 '18 at 12:59
  • 1
    An administrator might need to [grant approval for the `readMavenPom().getVersion()`](https://stackoverflow.com/a/39412951/545127) – Raedwald Mar 30 '19 at 15:49
3

Had the same need and solved as suggested with Groovy parsing the pom.

import jenkins.util.*;
import jenkins.model.*;

def thr = Thread.currentThread();
def currentBuild = thr?.executable;
def workspace = currentBuild.getModuleRoot().absolutize().toString();

def project = new XmlSlurper().parse(new File("$workspace/pom.xml"))

def param = new hudson.model.StringParameterValue("project.version", project.version.toString())
currentBuild.addAction(new hudson.model.ParametersAction(param));

Add this script as a post step of type "Execute system Groovy script" (so it's not needed to install Groovy) and paste the code in the "Groovy command".

Sylvain
  • 876
  • 8
  • 11
  • See [project-version-from-maven-to-sonar-using-hudson](http://stackoverflow.com/questions/14691818/project-version-from-maven-to-sonar-using-hudson/15879456#15879456) – Sylvain Apr 08 '13 at 12:51
  • I've tried to use `${project.version}` in the shell script step after that and it failed: `bad substituion`, after I've renamed `project.version` to `project_version` - it worked. Seems jenkins don't allow `dot` in var names? – yetanothercoder May 20 '13 at 10:01
  • This does not seem to work in Jenkins 2.46+. Although Groovy extracts the version fine, the `project.version` environment variable is empty in the rest of my Freestyle project. Is there something that changed in Jenkins 2+ that would make this obsolete? I'm trying to access the var subsequently as `${project.version}` – Eric B. Jul 26 '17 at 21:14
3

We used the Groovy Postbuild Plugin.

    String regex = '.*\\[INFO\\] Building .+ (.+)';
    def matcher = manager.getLogMatcher(regex);
    if (matcher == null) {
        version = null;
    } else {
        version =  matcher.group(1);
    }

Adding this to Jenkins for use later is a bit tricky. Give this a shot, although I remember this causing us some headaches. (Sorry, we did this a long time ago)

def addBuildParameter(String key, String value) {
    manager.build.addAction(new hudson.model.ParametersAction(new hudson.model.StringParameterValue(key,value))); 
}
Roy Truelove
  • 22,016
  • 18
  • 111
  • 153
2

Execute the Maven Plugin "exec-maven-plugin" in "Execute Shell" as a "Conditional step" worked for me:

mvn -q -Dexec.executable="echo" -Dexec.args='${projects.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec

Integrate in Jenkins:

-> "Add post-build step"
    -> "Conditional steps (single or multiple)"
        -> "Execute Shell:"

export MY_POM_VERSION=`mvn -q -Dexec.executable="echo" -Dexec.args='${projects.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.3.1:exec` && [[ "${MY_POM_VERSION}" == "THE_VERSION_TO_BE_MATCHED" ]] && echo "CONDITION_IS_MET"

    -> "Steps to run if condition is met"
        -> Add any build step you need

Notes:

  • THE_VERSION_TO_BE_MATCHED has to exchanged with your version
  • '&& echo "CONDITION_IS_MET"' is only for debugging purposes. For the same purpose you can add a '&& echo "MY_POM_VERSION=${MY_POM_VERSION}"' after the mvn command in order to understand what's going on.

This approach is more reliable than a "grep" and it could be an alternative if the Jenkins Ruby Plugin is not installed.

Jan M
  • 447
  • 5
  • 9
0

You could also do :

MAVEN_VERSION=`grep A -2 -B 2 "<your_project_name>" pom.xml | grep version | cut -d\> -f 2 | cut -d\< -f 1`-commit-"`echo $GIT_COMMIT`"

Explanation: assuming that you have your project name within a line or two above/below version like a normal pom:

<groupId>org.apache.bigtop</groupId>
<artifactId>bigpetstore</artifactId>
<version>1.0-SNAPSHOT</version>

Then you can easily grep for the artifactId, use the "before/after" grep actions to suck in the version with it, and then grep the version out and use the simple unix "cut" command to splice out the content between "version" tags.

I like the Jenkins-groovy integration, but this is alot easier and will work even on a build server which you dont have control over (i.e. because bash is universal).

jayunit100
  • 17,388
  • 22
  • 92
  • 167
  • This worked like a charm but fetched multiple version-infos, because auf the dependencies which also contain a version-tag. Adding a head before the grep and pipe it worked for me MAVEN_VERSION=`head "" pom.xml | grep A -2 -B 2 | grep version | cut -d\> -f 2 | cut -d\< -f 1`-commit-"`echo $GIT_COMMIT`" – JohannesDienst Jul 10 '14 at 06:58
  • @JoeDred I still get multiply versions. But my "another version" is not from dependencies, but from properties: **cz.px.iis iis-core-ng jar 2.1.1-SNAPSHOT 7.7.5 UTF-8 1.8 1.8 ** and output is __2.1.1-SNAPSHOT 7.7.5__ – agad Jan 18 '17 at 15:43
  • @agad Maybe you should limit the lines you pipe to the first grep. Head takes an optional argument -n or --lines. Try **head --help** which shows how it is used. – JohannesDienst Jan 20 '17 at 07:03
0

Solution:

POM_VERSION=$( \
    xmlstarlet sel \
    -N x='http://maven.apache.org/POM/4.0.0' \
    -t \
    -v '//x:project/x:version/text()' \
    pom.xml \
)

Explanation:

You can do this in a one-liner using a command-line XPath tool, such as those mentioned at "How to execute XPath one-liners from shell?". I chose XMLStarlet, but they all have similar syntax.

When parsing a POM, you have to account for namespaces. The docs here helped me figure this out.

In order to get the text for an element in XPath, you use the text() function as explained at XPath: select text node.

My POM looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.foo.bar</groupId>
    <artifactId>foobar</artifactId>
    <version>1.0.6-SNAPSHOT</version>
    <packaging>jar</packaging>

The downside here is that if the namespace changes, you have to change the command.

Community
  • 1
  • 1
John Michelau
  • 1,001
  • 8
  • 12
0

Using 'Execute System Groovy Script' as follows:

import jenkins.util.*;
import jenkins.model.*;

def thr = Thread.currentThread();
def currentBuild = thr?.executable;
def projectManager = build.getProject()
def file = projectManager.getWorkspace().child("pom.xml");
def project = new XmlSlurper().parseText(file.readToString())

def param = new hudson.model.StringParameterValue("currentVersion", project.version.toString())
currentBuild.addAction(new hudson.model.ParametersAction(param));

By using Execute System Groovy script you have direct access to the build, from which you can get the project and thus the "child" file in this case pom.xml.

You won't have to create a new file and as you can see it offers very powerful access to every file within the workspace.

  • Thank you for this code snippet, which might provide some limited short-term help. A proper explanation [would greatly improve](//meta.stackexchange.com/q/114762) its long-term value by showing *why* this is a good solution to the problem, and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you've made. – Toby Speight Apr 24 '18 at 14:03
0

Based on @Akom`s answer the pre steps to have POM_VERSION are:

  1. "Inject environment variables" with property file your_property_file. Note if you select "Inject environment variables to the build process" the file needs to exist in the jenkins workspace.
  2. run in a pre step execute shell the follwing bash script.

Script

mvn org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.version -l project_version
# grep for the version pattern rather than not mentioning '\['
echo "POM_VERSION=$(grep -E  '^[0-9.]+(-SNAPSHOT)?$' project_version)" > your_property_file
Adam
  • 2,800
  • 1
  • 13
  • 15