75

We have a workflow requirement that essentially mean that we need to have the artifact version of a module externally defined from the current branch in git.

I.e. if we are on the master branch in git, I need <version>master-...</version> and if we are on the bugfixX branch, I need <version>bugfixX-....</version> for the generated artifact for this pom.xml.

I have previously found that https://github.com/koraktor/mavanagaiata can provide the SHA-1 hash as a property and it appears from the documentation that it can also provide the branch, so perhaps if it could be run early enough in the process we could set the property and just put <version>${our.version}</version> in the pom. If this is possible, I would very much like to see a working pom.xml (and reward a 500 point bounty for it too).

If not, I guess we are into either preprocessing or "git checkout" do extra magic with some of the hooks (which I have not yet tried, working code would be great too).

We have a top level pom, which can be run to generate a property file in ".." before building the modules where this functionality I'm asking about needs to go.

Any suggestions on how to solve this?

Koraktor
  • 41,357
  • 10
  • 69
  • 99
Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
  • What does this artifact used for (a simple library jar dependency or deployable executable like war or ear)? How do you build/release this artifact at the moment (maven-release-plugin or something else)? – yorkw Nov 27 '12 at 21:05
  • This can currently be jars and wars. We currently look at snapshots only. – Thorbjørn Ravn Andersen Nov 28 '12 at 00:37
  • As far as I know, there's currently no way to dynamically generate your `project.version` using a plugin like Mavanagaiata. I'd love to do this myself, so it would be easy to install artifacts with a name `git describe`, which would be a truly unique identifier like `my-artifact-1.2.3-5-gdeadbeef`. – Koraktor Dec 17 '12 at 21:48
  • It sounds like you're looking for a way to change the project version within a single Maven run. Is that right? As far as I know, Maven never has and never will support that. I achieve something similar in my CI builds, but it requires two, separate invocations of Maven. – Ryan Stewart Dec 18 '12 at 01:25
  • On the other hand, exactly what is the scope of where you need your custom version to be seen? I can imagine a way, by overriding a lot of Maven's built-in conventions, that a single build could generate and "use" a separate version from what's shown in the pom...for some definition of "use". – Ryan Stewart Dec 18 '12 at 01:46
  • I basically need that when I check out a branch in git, that all future maven activity in that checkout refers to that branch. both from IDE use but also CLI. – Thorbjørn Ravn Andersen Dec 18 '12 at 07:12
  • @yorkw I noticed I did not answer fully. We have a two step process - building the individual artifacts and assembling artifacts into a customer specific deployment. I would like to use this mechanism in both steps. – Thorbjørn Ravn Andersen Dec 19 '12 at 08:55
  • 1
    By using classifier (supported by many plugin like maven-jar-plugin or maven-assembly-plugin) with version together can give your version-classifier e.g. 1.0.0-SNAPSHOT-MASTER. Not sure if this is what you looking for. There are probably some smart way to scriptly adjust the value in pom.xml based on git location. – yorkw Dec 19 '12 at 11:00
  • 1
    Have you looked into smudge filters and using those to store the branch name on checkout? – Chronial Dec 19 '12 at 17:53
  • @chronial no I have not. Perhaps you would like to write an answer listing the steps you think of. – Thorbjørn Ravn Andersen Dec 20 '12 at 14:06
  • 1
    Had a closer look – you can’t use them for this purpose, as the branch info is not yet available during checkout – Chronial Dec 21 '12 at 13:25
  • 2
    It appears from http://maven.apache.org/docs/3.2.1/release-notes.html that Maven 3.2.1 allows some (probably not all) property expressions in the version tag. "A simple change to prevent Maven from emitting warnings about versions with property expressions. Allowed property expressions in versions include ${revision}, ${changelist}, and ${sha1}. These properties can be set externally, but eventually a mechanism will be created in Maven where these properties can be injected in a standard way.". I have not tested it yet. – Thorbjørn Ravn Andersen May 09 '14 at 08:51

7 Answers7

59

Update 2022: Alternative solutions were brought into existence since this answer was written 2012. While the Git-hook-based solution presented here still holds (with its pros and cons), some good answers here present plugin-based or even pure Maven solutions which I recommend to consider.


Indeed, [note 2022: unless some plugins are added to the project], Maven can't change the version of it's own project in one run with other goals. On top of it, raw Maven doesn't support arbitrary properties in the <version> tag. Therefore, a separate execution is required to run a goal which will change the version of the POM. There are various plugins which can do it. For example: versions:set goal from versions plugin - http://mojo.codehaus.org/versions-maven-plugin/set-mojo.html

So, you might execute it as follows:

mvn versions:set -DgenerateBackupPoms=false -DnewVersion=$branch-SNAPSHOT

where the $branch variable has to contain current Git branch name; it can be extracted with git rev-parse, like this:

branch=$(git rev-parse --abbrev-ref HEAD)

But still, one needs to execute it somehow. You can do manually, but it is cumbersome. So, my guess is that indeed the most robust solution would be to approach this from Git side. That is - a Git hook. Here is the complete Git post-checkout hook which will do the job (same code as above with some filtering to run the hook only when the branch is checked out, not the individual files only):

#!/bin/bash

echo 'Will change the version in pom.xml files...'

# check if the checkout was to checkout a branch
if [ $3 != '1' ]
    then echo 'git checkout did not checkout a branch - quitting';exit
fi

# get current branch name
branch=$(git rev-parse --abbrev-ref HEAD)
version=$branch-SNAPSHOT

# run maven versions plugin to set new version
mvn versions:set -DgenerateBackupPoms=false -DnewVersion=$version

echo 'Changed version in pom.xml files to $version'

Put this content to the file PROJECTDIR\.git\hooks\post-checkout file. Note that the hook file should be executable to run it (chmod +x post-checkout).

Few notes about versions plugin - it is pretty flexible and supports many options and have few other goals which might be of help, depending on your project structure (do you use parent poms or not, do childs have their own versions or do they derive from parent, etc.). So, the hook above might be modified slightly to support you specific case by using other goals from versions plugin or by specifying additional parameters.

Pros:

  • Robust
  • No need to change anything in the pom.xml files themselves to make this work
  • This "functionality" can be turned off simply by deactivating the hook (remove or make not executable) - again, no changes are required in the pom.xml

Cons:

  • One can't enforce others to use a hook - it should be installed manually after the repo is cloned (or, you can provide a script to install the hook if supposed Git users are afraid of touching the stuff inside .git directory).

More elaborate version of the Bash script for the Git hook:

Hereafter is the more complicated version of the hook, which will not only set the version to the branch name, but will also preserve the suffix of the old version. For example, provided old version master-1.0-SNAPSHOT, switching to feature1 branch will change the version of the project to feature1-1.0-SNAPSHOT. This bash script suffers from few problems (requires branch names without dash symbol (-) in the name, and only takes the version of the root pom), but may give an idea of how the hook may be extended: given a mix of mvn and bash commands you can extract and update quite a lot of the information in the POM.

#!/bin/bash

echo 'Will change the version in pom.xml files...'

# check if the checkout was to checkout a branch
if [ $3 != '1' ]
    then echo 'git checkout did not checkout a branch - quitting';exit
fi

# get current branch name
branch=$(git rev-parse --abbrev-ref HEAD)

# get current version of the top level pom
current_version=$(mvn help:evaluate -Dexpression=project.version | grep -v '\[.*')

# extract version suffix
suffix=$(echo $current_version | cut -d \- -f 2)

# build new version
version=$branch-$suffix

# run maven versions plugin to set new version
mvn versions:set -DgenerateBackupPoms=false -DnewVersion=$version

echo 'Changed version in pom.xml files to $version'
Tim
  • 12,318
  • 7
  • 50
  • 72
  • 1
    If you can get hold of the previous branch name, you can solve the dash problem using something like: `${NEW_BRANCH_NAME}-${CURRENT_VERSION#${CURRENT_BRANCH_NAME}-}`. – Sergiu Dumitriu Dec 21 '12 at 04:45
  • The only approach looking reasonable is to manually setting the version after branching, so that is what we will need to do for now. Hopefully maven 3.1 or 3.2 will provide more of what we need. – Thorbjørn Ravn Andersen Dec 24 '12 at 16:05
  • 1
    +1 But doesn't it make more sense to have the hook post-merge and post-branch rather than post-checkout? – Codey McCodeface Dec 13 '13 at 15:13
  • 1
    The answer is quite old and now (2016) easier & better alternative could be used, see my answer about [jgitver-maven-plugin](https://github.com/jgitver/gradle-jgitver-plugin) – Matthieu BROUILLARD Jun 24 '16 at 07:44
  • 1
    Since maven-3.5.0 there is support for a limited set of properties in the version tag. See my answer below, which requires no additional plugins. – Ed Randall Sep 22 '18 at 15:59
22

Sorry to revive this question and to post very lately another solution but it is possible indeed to change maven version dynamically and to use some git features, a little bit like git describe would do.

The project that does this is jgitver-maven-plugin (disclaimer; I am the author), it uses jgitver a jgit based library in order to derive maven project version from git information .

enter image description here

It is very easy to use as a maven extension

...
  <build>
      <extensions>
          <extension>
              <groupId>fr.brouillard.oss</groupId>
              <artifactId>jgitver-maven-plugin</artifactId>
              <version>0.1.0</version>
          </extension>
      </extensions>
     ...
  </build>
...

The extension can also be used as a plugin extension, and then allows more configuration , for example in case you would not like to use SNAPSHOTS. See the project page for a description of full usage scenarios.

There is also a gradle plugin available that does more or less the same.


[edit 1]: answer to Thorbjørn Ravn Andersen comment

The plugin does not modify the original pom files or the build.gradle files.
For the maven plugin, modifications are done both in memory in the maven Object Model and written to a temporary file in temp directory. The calculation is based on the git metadata only (tags, commits, ...).
This non modification allows to not pollute the git history. WHen your are satisfied with a git commit, tag it git tag -a x.y.z and mvn deploy : that's all.
The version in your project files, is now useless and can be set to 0 for example.

As of today, and due to IDEA-155733 only recent EAP versions of IntelliJ work with the maven plugin. Eclipse & Netbeans have no problem.


Matthieu BROUILLARD
  • 1,991
  • 1
  • 16
  • 25
  • Could you please elaborate in your answer on how this works? Are file changed on disk or is all this in memory? – Thorbjørn Ravn Andersen Jun 24 '16 at 08:19
  • After having a look at your documentation and your explanation I still don't have any idea how this works and what it solves. I would suggest reworking the documentation so users can understand your plugin without cloning and running. – Thorbjørn Ravn Andersen Jun 29 '16 at 06:39
  • I'll try to but what is not understandable from your point of view? Readme of the project start with: _This plugin allows to define the pom version of your project using the information from your git history. It calculates the version, a little bit like git describe would do but in a more efficient way for maven projects_ Isn't that clear enough? – Matthieu BROUILLARD Jun 29 '16 at 10:53
  • Show actual examples. – Thorbjørn Ravn Andersen Jun 29 '16 at 11:42
  • @ThorbjørnRavnAndersen In a case of my project: after installing the plugin and invoking maven install, it shows something like that: `jgitver-maven-plugin is about to change project(s) version(s) pl.gendoria::rpgmv-translation-extractor::1.0.0-SNAPSHOT -> 0.0.0-SNAPSHOT`. 1.0.0-SNAPSHOT is a build number declared in POM. The rest of the build uses 0.0.0-SNAPSHOT as a version number, plugin also switched ${project.version} variable accordingly. – Tomasz Struczyński Feb 14 '17 at 10:55
  • @TomaszStruczyński looks like a normal behavior of [jgitver-maven-plugin](https://github.com/jgitver/jgitver-maven-plugin). Version could have been "0" or "empty" it would have been the same ; it is calculated from git history not from pom. Please open an issue on [jgitver-maven-plugin](https://github.com/jgitver/jgitver-maven-plugin/issues) I'll explain you how it works. – Matthieu BROUILLARD Feb 14 '17 at 15:08
  • @MatthieuBROUILLARD - yes, it does look normal. I just provided actual example for ThorbjørnRavnAndersen :) – Tomasz Struczyński Feb 14 '17 at 15:43
19

Disclaimer: I am the author

My maven core extension will virtually set the version based on the current branch or tag. You can config custom version format patterns as you like.

https://github.com/qoomon/maven-git-versioning-extension

Version Format Example enter image description here

qoomon
  • 4,549
  • 1
  • 21
  • 27
9

From maven-3.5.0 on there is support for ${revision}, ${sha1} and ${changelist} properties within the version tag. This feature may be sufficient for your purpose if, for example you want to incorporate the Git branchname into the version for a CI build job. See Maven CI Friendly Versions

Basically, in your pom.xml replace the fixed version by:

<version>${revision}${changelist}</version>

Set default values for revision and changelist in the project root dir by creating a file .mvn/maven.config containing:

-Drevision=1.2.3
-Dchangelist=-SNAPSHOT

Check this file into version control, update it when you bump your project revision.

In your CI system you can then override the changelist variable using a cleaned-up representation of the Git branch name, eg.

# sed regex replaces non-(alphanumeric, dot or dash) char sequences with a dash
BRANCHNAME=$(git rev-parse --abbrev-ref HEAD | sed -E -e 's@[^0-9A-Za-z.-]+@-@g')
mvn clean install -Dchangelist="-${BRANCHNAME}"

(You may prefer git symbolic-ref --short HEAD for fetching the branchname, YMMV)

Your artifact built by the CI system for branch feature/branchname will then have a versioned-branch suffix like:

yourproject-1.2.3-feature-branchname.jar

whilst developers who do not use any overrides will still build it as:

yourproject-1.2.3-SNAPSHOT.jar
Ed Randall
  • 6,887
  • 2
  • 50
  • 45
  • @TT-- We don't want any slashes or whitespace in the maven artifact version. Slashes would denote a subdirectory! Our Git branchnames frequently contain slashes and spaces - but they don't ever contain backslashes though; If yours did, feel free to replace them too. You could include the backslashes in the existing character sets, no need for 2 sed expressions `| sed -e 's@[\\/ ][\\/ ]*@-@g'`. (The [ ] expression is doubled since my version of sed supports * but not + and I want to collapse multiple runs into a single dash). – Ed Randall Jan 18 '19 at 13:09
  • I hadn't noticed that `--abbrev-ref` did anything with slashes. It returns the current branch name - in our repo branches are all named `release/something`, `bugfix/something` or `feature/something` and `git rev-parse --abbrev-ref HEAD` returns exactly that. – Ed Randall Jan 18 '19 at 16:25
  • see: https://stackoverflow.com/questions/6245570/how-to-get-the-current-branch-name-in-git - second (most upvoted) answer – Ed Randall Jan 18 '19 at 16:32
  • I've updated the answer with safer 'sed' expression, which explicitly limits to the wanted character set only. -E allows modern REs. – Ed Randall Jan 18 '19 at 16:50
  • 1
    Thanks for further clarifying, my earlier thought was incorrect, and now I see what you're doing. Your edit is appreciated, I think that's exactly what I was looking for, to handle all cases. – TT-- Jan 18 '19 at 16:52
  • The main trouble with doing this, is that it works from the command-line and it works in Jenkins, but a developer using an IDE to build (ie. Eclipse) has trouble with it. – Ed Randall Jan 18 '19 at 16:56
4

If it is sufficient to set the git tag and version information in the artifact file name, you can use maven-jgit-buildnumber-plugin:

<build>
  <finalName>${artifactId}-${git.buildnumber}</finalName>
  <plugins>
    <plugin>
      <groupId>ru.concerteza.buildnumber</groupId>
      <artifactId>maven-jgit-buildnumber-plugin</artifactId>
      <version>1.2.7</version>
      <executions>
        <execution>
          <id>git-buildnumber</id>
          <goals>
            <goal>extract-buildnumber</goal>
          </goals>
          <phase>prepare-package</phase>
        </execution>
      </executions>
    </plugin>
    <!-- more plugins -->
  </plugins>
</build>
  • @burtsevyg what does it mean, that it does not work with maven-release-plugin? Could you please explain? – Gábor Lipták Mar 31 '16 at 13:19
  • When I try it I find that git variables and others not accessible in release:perform goal. I get them from git command line and then pass through -Darguments='' – burtsevyg Apr 01 '16 at 07:44
  • https://github.com/qoomon/maven-branch-versioning-extension will work with every plugin, cause it sets the version bevore any plugin is running. – qoomon Aug 23 '18 at 10:22
  • what about I want to have the same version onthe docker image? does the plugin handle it? – ttati Apr 03 '20 at 09:29
3

Have you checked the buildnumber-maven-plugin which gives you the opportunity to use the revision number of git. But you needed something different. Furthermore i would suggest to do a thing like:

   1.0.0-SNAPSHOT 
   1.0.0-SNAPSHOT 

beeing on master

on a branch you can simple change the version to

  1.0.0-BF-SNAPSHOT 
khmarbaise
  • 92,914
  • 28
  • 189
  • 235
2

Have you tried using this plugin?: https://github.com/ktoso/maven-git-commit-id-plugin. You can configure it to generate a properties file with all the relevant info about your repo state:

  • branch
  • describe
  • commitId
  • buildUserName
  • buildUserEmail
  • buildTime
  • commitUserName
  • commitUserEmail
  • commitMessageShort
  • commitMessageFull
  • commitTime
Chronial
  • 66,706
  • 14
  • 93
  • 99