Continuous Delivery with Maven
Based on todays requirements it is needed to do a continuous delivery of your artifacts by each change on your master (Git)/trunk(SVN). The question is what you need to achieve this with Maven?
Usually you are using so called SNAPSHOT
versions which means your version
looks like 1.2.3-SNAPSHOT
and indicates that you are currently working
towards the release version 1.2.3
. Afterwards you continue with the next
snapshot version like 1.2.4-SNAPSHOT
and so on.
Depending on your environment this has been changed which means that each change
you have made on master (Git) you need to produce a new release version which
means 1.2.3
and next will be 1.2.4
. The development will be done on branches
where you might need a SNAPSHOT version.
Let us take a look into a single module pom file which looks like this:
1<project
2 xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
6 <modelVersion>4.0.0</modelVersion>
7
8 <parent>
9 <groupId>com.soebes.smpp</groupId>
10 <artifactId>smpp</artifactId>
11 <version>4.0.1</version>
12 </parent>
13
14 <groupId>com.soebes.examples.j2ee</groupId>
15 <artifactId>domain</artifactId>
16 <version>1.2.3-SNAPSHOT</version>
17...
18</project>
The version is defined with 1.2.3-SNAPSHOT
if you now call:
1mvn clean package
You will produce artifacts which look like domain-1.2.3-SNAPSHOT.jar
.
Unfortunately this kind of artifact is something which can change over the time
based on Maven philosophy. That means if you now make a change in your code
and just do it again mvn clean package
it will create the
same domain-1.2.3-SNAPSHOT.jar
file. There is no real difference if you
do an mvn clean install
instead. This will only install the created
artifact into your local cache $HOME/.m2/repository
. Furthermore if you do
mvn clean deploy
the difference is only that each time you do this on your repository
manager the naming schema is a little bit different which means the artifact will
look like domain-1.2.3-20180821.202345-1.jar
. So in the end the artifact contains
a time stamp. But the most important thing is that SNAPSHOT
versions
are not immutable as you might have already realized based on the above.
So the only way to create real immutable versions is to create releases from Maven
point of view. This means you have to use a version without -SNAPSHOT
.
So in consequence you have a pom file which looks like the following:
1<project
2 xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
6 <modelVersion>4.0.0</modelVersion>
7
8 <parent>
9 <groupId>com.soebes.smpp</groupId>
10 <artifactId>smpp</artifactId>
11 <version>4.0.1</version>
12 </parent>
13
14 <groupId>com.soebes.examples.j2ee</groupId>
15 <artifactId>domain</artifactId>
16 <version>1.2.3</version>
17...
18</project>
So you can do a mvn clean deploy
and you have created an immutable
release artifact domain-1.2.3.jar
. If you try to do this a second time your
repository manager will tell you that this does not work cause by definition
releases are immutable which means you can not overwrite them. So the
consequence is if you have made a change/mistake/fix to your project you have to
change the version number. Ok, not very complicated just change the version
to 1.2.4
commit and do another mvn clean deploy
? So good so far.
Some people do not like the commits where only a version has been changed. So the question is: Is there a more convenient way to handle this?
Starting with Maven 3.5.0+(I recommend to use the most recent version) there is a more convenient way to handle that. You can write your pom file like the following:
1<project
2 xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
6 <modelVersion>4.0.0</modelVersion>
7
8 <parent>
9 <groupId>com.soebes.smpp</groupId>
10 <artifactId>smpp</artifactId>
11 <version>4.0.1</version>
12 </parent>
13
14 <groupId>com.soebes.examples.j2ee</groupId>
15 <artifactId>domain</artifactId>
16 <version>${revision}</version>
17
18...
19</project>
So now you can simply create release artifacts by using the following:
1mvn -Drevision=1.2.3 clean package
This will only create the artifacts in the target
directory of your project.
If you like to deploy them into a repository there are enhancements needed:
1<project
2 xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
6 <modelVersion>4.0.0</modelVersion>
7
8 <parent>
9 <groupId>com.soebes.smpp</groupId>
10 <artifactId>smpp</artifactId>
11 <version>4.0.1</version>
12 </parent>
13
14 <groupId>com.soebes.examples.j2ee</groupId>
15 <artifactId>domain</artifactId>
16 <version>${revision}</version>
17
18 <build>
19 <plugins>
20 <plugin>
21 <groupId>org.codehaus.mojo</groupId>
22 <artifactId>flatten-maven-plugin</artifactId>
23 <version>1.0.0</version>
24 <configuration>
25 <updatePomFile>true</updatePomFile>
26 </configuration>
27 <executions>
28 <execution>
29 <id>flatten</id>
30 <phase>process-resources</phase>
31 <goals>
32 <goal>flatten</goal>
33 </goals>
34 </execution>
35 <execution>
36 <id>flatten.clean</id>
37 <phase>clean</phase>
38 <goals>
39 <goal>clean</goal>
40 </goals>
41 </execution>
42 </executions>
43 </plugin>
44 </plugins>
45 </build>
46</project>
So from now on you can create a release by using the following command:
1mvn -Drevision=1.2.3 clean deploy
But of course a version can only be used once. So if you like to make another release you have to change the version but now it is really easy:
1mvn -Drevision=1.2.4 clean deploy
Currently there are three properties supported which can be used inside a version tag:
- revision
- sha1
- changelist
Only these three properties can be used inside a version tag. You can of course use combinations of them like this:
1<version>${revision}${sha1}${changelist}</version>
If you use different properties you will get a warning by Maven. I strongly encourage users to take notice of the warnings and of course clean them up.
So what about a multi module build? There you can use them as well. The parent will look like this:
1<project
2 xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
6 <modelVersion>4.0.0</modelVersion>
7
8 <parent>
9 <groupId>com.soebes.smpp</groupId>
10 <artifactId>smpp</artifactId>
11 <version>4.0.1</version>
12 </parent>
13
14 <groupId>com.soebes.examples.j2ee</groupId>
15 <artifactId>parent</artifactId>
16 <version>${revision}</version>
17 <packaging>pom</packaging>
18 ..
19 [enhanced described earlier]
20 ..
21 <modules>
22 <module>webgui</module>
23 <module>app</module>
24 <module>domain</module>
25 <module>service</module>
26 <module>service-client</module>
27 <module>appasm</module>
28 <module>assembly</module>
29 <module>shade</module>
30 </modules>
31</project>
and a child for example domain
will look like this:
1<project
2 xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
6 <modelVersion>4.0.0</modelVersion>
7
8 <parent>
9 <groupId>com.soebes.examples.j2ee</groupId>
10 <artifactId>parent</artifactId>
11 <version>${revision}</version>
12 </parent>
13
14
15 <artifactId>domain</artifactId>
16 ..
17</project>
You can now build the whole project from the root level by using the command:
1mvn -Drevision=1.7.0-SNAPSHOT clean package
The output will look like this:
1[INFO] Scanning for projects...
2[INFO] ------------------------------------------------------------------------
3[INFO] Reactor Build Order:
4[INFO]
5[INFO] parent [pom]
6[INFO] domain [jar]
7[INFO] service-client [jar]
8[INFO] webgui [war]
9[INFO] service [ejb]
10[INFO] app [ear]
11[INFO] appasm [pom]
12[INFO] shade [jar]
13[INFO] assembly [pom]
14[INFO]
15[INFO] ------------------< com.soebes.examples.j2ee:parent >-------------------
16[INFO] Building parent 1.7.0-SNAPSHOT [1/9]
17[INFO] --------------------------------[ pom ]---------------------------------
18[INFO]
19....
20[INFO]
21[INFO] -----------------< com.soebes.examples.j2ee:assembly >------------------
22[INFO] Building assembly 1.7.0-SNAPSHOT [9/9]
23[INFO] --------------------------------[ pom ]---------------------------------
24[INFO]
25[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ assembly ---
26[INFO] Deleting /Users/kama/ws-git/javaee/assembly/target
27[INFO]
28[INFO] --- flatten-maven-plugin:1.0.0:clean (flatten.clean) @ assembly ---
29[INFO] Deleting /Users/kama/ws-git/javaee/assembly/.flattened-pom.xml
30[INFO]
31[INFO] --- jacoco-maven-plugin:0.8.1:prepare-agent (default) @ assembly ---
32[INFO] argLine set to -javaagent:/Users/kama/.m2/repository/org/jacoco/org.jacoco.agent/0.8.1/org.jacoco.agent-0.8.1-runtime.jar=destfile=/Users/kama/ws-git/javaee/assembly/target/jacoco.exec
33[INFO]
34[INFO] --- flatten-maven-plugin:1.0.0:flatten (flatten) @ assembly ---
35[INFO] Generating flattened POM of project com.soebes.examples.j2ee:assembly:pom:1.7.0-SNAPSHOT...
36[INFO]
37[INFO] --- maven-site-plugin:3.7.1:attach-descriptor (attach-descriptor) @ assembly ---
38[INFO] No site descriptor found: nothing to attach.
39[INFO]
40[INFO] --- maven-assembly-plugin:3.0.0:single (assemblies) @ assembly ---
41[INFO] Reading assembly descriptor: jar-with-prod.xml
42[INFO] Reading assembly descriptor: assembly.xml
43[INFO] Reading assembly descriptor: jar-with-dev.xml
44[INFO] Building jar: /Users/kama/ws-git/javaee/assembly/target/assembly-1.7.0-SNAPSHOT-prod.jar
45[INFO] Building zip: /Users/kama/ws-git/javaee/assembly/target/assembly-1.7.0-SNAPSHOT-archive.zip
46[INFO] Building jar: /Users/kama/ws-git/javaee/assembly/target/assembly-1.7.0-SNAPSHOT-dev.jar
47[INFO] ------------------------------------------------------------------------
48[INFO] Reactor Summary:
49[INFO]
50[INFO] parent 1.7.0-SNAPSHOT .............................. SUCCESS [ 1.535 s]
51[INFO] domain ............................................. SUCCESS [ 0.876 s]
52[INFO] service-client ..................................... SUCCESS [ 0.111 s]
53[INFO] webgui ............................................. SUCCESS [ 0.474 s]
54[INFO] service ............................................ SUCCESS [ 0.454 s]
55[INFO] app ................................................ SUCCESS [ 0.282 s]
56[INFO] appasm ............................................. SUCCESS [ 0.237 s]
57[INFO] shade .............................................. SUCCESS [ 0.443 s]
58[INFO] assembly 1.7.0-SNAPSHOT ............................ SUCCESS [ 1.238 s]
59[INFO] ------------------------------------------------------------------------
60[INFO] BUILD SUCCESS
61[INFO] ------------------------------------------------------------------------
62[INFO] Total time: 6.292 s
63[INFO] Finished at: 2018-08-26T15:57:12+02:00
64[INFO] ------------------------------------------------------------------------
So it is now really easy to create a release from the whole project by using the following command:
1mvn -Drevision=1.7.0 clean deploy
This would result in a release deployed to a repository manager. So now let us take a look onto using it in a continious integration solution like Jenkins. On Jenkins you have often the situation to produce artifacts on a branch base (maybe SNAPSHOT based to be able to deploy them to test environments). So you can combine the properties like this:
1<project
2 xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
6 <modelVersion>4.0.0</modelVersion>
7
8 <parent>
9 <groupId>com.soebes.smpp</groupId>
10 <artifactId>smpp</artifactId>
11 <version>4.0.1</version>
12 </parent>
13
14 <groupId>com.soebes.examples.j2ee</groupId>
15 <artifactId>domain</artifactId>
16 <version>${revision}${sha1}${changelist}</version>
17
18 <properties>
19 <revision>1.0.0</revision>
20 <sha1/>
21 <changelist/>
22 </properties>
23...
24</project>
In your jenkins pipeline you can extract the branch name and insert it into the build call like this
1mvn -Dsha1=-BRANCHNAME -Dchangelist=-SNAPSHOT clean deploy
This approach has the advantage that on each branch you are using a different version which
are distinguished by the -BRANCHNAME
and those versions are snapshots. This is convenient
cause, all repositoy managers have support to delete snapshot versions after some time automatically.