19

How to update the version of child modules? There are a lot of Stackoverflow questions like this but I was not able to find one that fit this exact scenario... would love if this is a duplicate.

Consider the following project.

parent
  --mod1
  --mod2

At the beginning of a dev release cycle I need to update the parent and modules to the same version. If the version of the parent and modules stayed the same throughout the release then I would would just omit the <version> tag from the modules and execute versions:set -DnewVersion=1.1.1 to kick off the dev cycle. But as it turns out the modules do not all end the cycle with the same version. As bugs and fixes materialize only those modules with the bugs and such get updated. For example the parent and mod2 might be at version 1.1.1-RC1, but mod1 might be at 1.1.1-RC2.

As such I need to:
1) Include a <version> tag in the modules to track each modules version independently.

2) If mod2 requires mod1 as a dependency I need to make sure mod2 references the latest version of mod1.

This leads to the following two questions.

1) At the beginning of the cycle how can I set the parent and modules to the same version in one maven command? I tried version:set -DnewVersion=1.1.1, but this only updates the parent's version across all the POM's but not the module's version. I also tried -N versions:update-child-modules, but I think I am using it wrong because it does nothing, just shows skipped for all the modules.

2) This is a bit harder and matches to item 2 above. How do I update both mod1's version and mod2's reference to mod1's version in one step? I know how to do it in 2 steps:

parent pom:

<properties>
    <!-- update this manually if mod1's version no longer matches parent -->
    <mod1.version>${project.version}</mod1.version>
</properties>

mod2 pom:

    <dependency>
        <groupId>com.xxx</groupId>
        <artifactId>mod1</artifactId>
        <version>${mod1.version}</version>
    </dependency>

When mod1 bumps up to 1.1.1-RC2 I update the parent POM and mod1 POM to reflect this. These are two steps. Anyway to turn it into one step?

My example was small but in real life there are many modules which makes this important time saver, plus I'm curious.

A_Di-Matteo
  • 23,746
  • 7
  • 79
  • 112
Jose Martinez
  • 9,918
  • 6
  • 41
  • 60
  • I would suggest changing your procedure and doing new RCs of all modules even of only some of them change. Sync your release cycle and let "release:prepare" handle the version updating for you. – rec Aug 27 '15 at 19:31
  • @rec Thanks for the advice. This was already on the table as an option. Any idea how to accomplish what is being requested? – Jose Martinez Aug 27 '15 at 19:41
  • I have seen a `${mod1.version}` variable set in the parent pom, then called in the child poms `${mod1.version}` and for dependencies, although from Maven 3's warnings, I am sure there are many good reasons not to do this. – Rossiar Sep 01 '15 at 09:46
  • @Rossiar, you know I tried that at first. I was getting an error so I thought it couldn't be done. But I tried it again and the reason why it was not working was because I had set `mod1.version` to `project.version`, in the parent's POM, and that lead to a recursive error. When I set `mod1.version` to a constant then it works. Now I just need a way to set all the `modX.version`'s and the parent's version to the same value at the beginning of a release cycle. – Jose Martinez Sep 01 '15 at 16:37
  • @JoseMartinez Using that approach the best way is sadly to manually change all the properties in the parent `pom.xml` before release. But I'm confused as to why you would want to up version sub-components for a major release when they hadn't actually changed? – Rossiar Sep 02 '15 at 08:31
  • @Rossiar, good point. So I guess two ways of doing it would be to leave each module at the version they were at organically, or version all modules and parent up together. Instead the client chose to version everything all modules up together at the beginning of a cycle but only then only up the version on the modules with bugs/fixes after RC1. I think this has to do with not wanting to redeploy all the modules. I'll bring up your suggestion in a meeting today. – Jose Martinez Sep 02 '15 at 11:06

4 Answers4

7

Question 1)

The best way to manage application lifecycle and releases is to use the release plugin.

As you may know, Maven philosophy is convention over configuration. Maven convention is to use snapshot versions (those ending with -SNAPSHOT) during development and assigning a non snapshot version only for releases.

Say you're developing version 1.1.1. While under development, you just use 1.1.1-SNAPSHOT. Maven will take care of updates of the snapshot. If using an artifact repository, you can use -U to make sure you always have the last version of the snapshots.

When the release is ready, release plugin generates and deploys version 1.1.1 and updates POM's with the new development version, such as 1.1.2-SNAPSHOT.

About multimodule projects, there are two scenarios: modules are related but independent (such as several web applications) or they are modules of a single big application or library and they share versions. You seem interested in the latter.

The best way in this case is just to inherit the same parent (maybe also root) module, including its version. You reference parent group:artifact:version and you do not specify a version for the children. Generally you also inherit group, so your child pom can look like:

<parent>
   <groupId>com.mycompany.myproject</groupId>
   <artifactId>myproject-parent</artifactId>
   <version>1.1.1-SNAPSHOT</version>
   <relativePath>../myproject-parent</relativePath>
</parent>
<artifactId>myproject-module1</artifactId>

Now you just need to take care of children pointing to the right version of the parent with the help of the release plugin.

To help it know about children, you should make your parent pom also a root pom by including the modules section as shown later.

Question 2) I usually declare properties in the parent with all versions of all artifacts which may be referenced. If several modules share version, you just need one property. The parent can look like:

<groupId>com.mycompany.myproject</groupId>
<artifactId>myproject-parent</artifactId>
<version>1.1.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
   <myproject.version>1.1.1-SNAPSHOT</myproject.version>
</properties>

.......

<modules>
   <module>../myproject-module1</module>
   ...
</modules>

Children can reference other modules using

<version>${myproject.version}</version>

It is a very bad practise to declare dependencies using LATEST. Say you do this for version 1.1.1. Now you're working with version 1.1.2-SNAPSHOT and probably you have artifacts with this version installed in your local repo.

Now say for some reason you need to rebuild version 1.1.1, for instance because of a bug in production. Your build will use the new version. If you're lucky, this will break the build. If you're unlucky it may even go unnoticed to production.

Last but not least, some people like using property values to declare children versions. This is strongly discouraged and will be reported as a warning by maven. I personally don't ever do it. The reasons are also related to reproducibility of builds and the fact that maven assumes that a release build will never change. Having a module version be externally tweakable is not really a good idea.

EDIT:

Case when module versions are not aligned.

Actually both scenarios can be mixed. You can have, for instance:

Parent

---Component1

---Component2

---Component3

------Comp3Module1

------Como3Module2

------Comp3Module3

Where parent and the three component versions are different and the three modules of component3 share its same version as explained before.

Question 1) In this case each module has its version independently specified. As said before, it's a bad practise to use a property to specify module version, reason why I can only recommend to literally specify versions. As already said, to manage versioning, the best way is to use release plugin, and integrate it with the version control system, such as SVN. Other answers give details on how to use it, so I won't elaborate it further, unless requested.

Question 2) The recommended approach is the same explained for the case of sharing the same version, only that you need several properties. The parent can look like:

<properties>
   <myproject.group>com.mycompany.myproject</myproject.group>
   <component1.version>1.1.1-RC1</component1.version>
   <component2.version>1.1.1-RC2</component2.version>
   <component3.version>2.0.0</component3.version>
<properties>

Then you can use dependency management to centralise version management in the parent.

For instance, in parent pom,

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>${myproject.group}</groupId>
         <artifactId>component1</artifactId>
         <version>${component1.version}</version>
      </dependency>
      <dependency>
        <groupId>${myproject.group}</groupId>
         <artifactId>component2</artifactId>
         <version>${component2.version}</version>
         <type>war</type>
      </dependency>
      <dependency>
         <groupId>${myproject.group}</groupId>
         <artifactId>comp3module1</artifactId>
         <version>${component3.version}</version>
        <type>ejb</type>
      </dependency>
      <dependency>
         <groupId>${myproject.group}</groupId>
         <artifactId>comp3module1</artifactId>
         <version>${component3.version}</version>
        <type>ejb-client</type>
      </dependency>
      <dependency>
         <groupId>${myproject.group}</groupId>
         <artifactId>comp3module2</artifactId>
         <version>${component3.version}</version>
        <type>war</version>
      </dependency>
   </dependencies>
</dependencyManagement>

Now, to reference any module from any other module, it's as easy as:

<dependency>
   <groupId>${myproject.group}</groupId>
   <artifactId>component1</artifactId>
</dependency>
<dependency>
   <groupId>${myproject.group}</groupId>
   <artifactId>comp3module1</artifactId>
   <type>ejb-client</type>
</dependency>

Versions are automatically managed from the parent. You don't need to maintain them in children dependencies, which become also less verbose.

Juan
  • 368
  • 2
  • 6
  • Juan thanks for the detailed response. Question 1 refers to each module having their own version. In your answer all the modules have the same version. Please note that we use -SNAPSHOT but at the end of a dev cycle when we go into UAT we use the non-SNAPSHOT versions. Same with question 2, it assumes each module will have the same version. The variance of versions within the child modules is at the root of the question. – Jose Martinez Jan 08 '17 at 14:31
  • Ok, I had understood that at the end of the cycle you realigned all versions to start new cycle with same version for all modules. I'll clarify the differences in this case. – Juan Jan 08 '17 at 14:36
  • Added info about independent versions. Hope it helps. – Juan Jan 08 '17 at 15:26
  • Have you seen my answer? – Jose Martinez Jan 08 '17 at 15:32
  • I didn't notice you answered your own question :) There are some differences in my proposal. Everything is managed from the parent pom, but you should change it, increment its version and let children point to the right parent version. You can make it easier through release plugin, but I don't recommend setting versions by changing properties when building. With dependency management, you don't need to touch anything else in children. – Juan Jan 08 '17 at 15:38
  • What you have in DependencyManagement accomplishes the same as what I have for properties, as far as centralization is concerned, which is why I pointed it out. Regardless of whether I put the child's version in properties or DependencyManagement, it is centrally controlled from the parent POM. – Jose Martinez Jan 08 '17 at 15:42
  • Yes, there are usually several ways to achieve the same results. I just share what can be achieved through best practices and from my own experience with maven. Neverthless, I don't quite get yes why you would like to maintain different versions across modules if you are making them equal for the next cycle. Say you just change one module and you only change its version to RC2. Only to update references from other modules, you nee to also update their version, as they are not the same if they depend on different module versions. Why not just update all them to RC2 even without changes? – Juan Jan 08 '17 at 17:11
  • 1
    I believe keeping versions aligned all the time would ease your life quite a lot. – Juan Jan 08 '17 at 17:12
  • I agree. For future projects for that client I was able to convince them of that. But for that older legacy project my hands were tied. – Jose Martinez Jan 08 '17 at 17:23
  • Oh yes, I know this situation. Hope you can take profit of any of the things I've shared :) – Juan Jan 08 '17 at 17:27
2

1) I've also tried the version:set in the past, but never got it working right. It's supposed to be doing the same process as release:prepare, but it actually doesn't. So what you could try is mvn release:prepare -DautoVersionSubmodules -DdryRun. That is supposed to make all the updates without checking anything into the repo and without making any tags.

2) I believe the ClearTK project once followed a similar strategy as you do: they maintained a multi-module project with each module having its own release cycle. To stay on top of the situation, they implemented a custom maven plugin to warn them about dependency version inconsistencies.

https://github.com/ClearTK/cleartk/tree/master/consistent-versions-plugin

While such a plugin would not make the updates that you request, it should at least notify you when updates are necessary. To really fix your problem, you might consider following a the same route as ClearTK did and implement your own Maven plugin (or you do what ClearTK eventually ended up doing: switching to a synced release cycle ;) )

rec
  • 8,872
  • 3
  • 24
  • 41
  • An issue with versions:set is that, if you have lots of submodules with different combinations selected by different profiles - you need to use a profile that selects *all* submodules in order to ensure that they are all updated. Having such a profile activeByDefault, this command then works fine: `mvn -DnewVersion=1.2.3-SNAPSHOT versions:set versions:commit` – Ed Randall Apr 21 '21 at 15:43
1

Ok this is what I came up with. This is based on this continuous-releasing-of-maven-artifacts article.

Parent POM:

<properties>
    <!-- versions of modules -->
    <main.version>1.0</main.version>
    <revision>SNAPSHOT</revision> <!-- default revision -->
    <Module1.revision>${revision}</Module1.revision>
    <Module2.revision>${revision}</Module2.revision>
    <Module3.revision>${revision}</Module3.revision>
    <Module4.revision>${revision}</Module4.revision>
    <Module5.revision>${revision}</Module5.revision>
    <Module1.version>${main.version}-${Module1.revision}</Module1.version>
    <Module2.version>${main.version}-${Module2.revision}</Module2.version>
    <Module3.version>${main.version}-${Module3.revision}</Module3.version>
    <Module4.version>${main.version}-${Module4.revision}</Module4.version>
    <Module5.version>${main.version}-${Module5.revision}</Module5.version>
</properties>

Sample child POM with inter project dependency:

    <groupId>com.xyz</groupId>
    <artifactId>Module4</artifactId>
    <packaging>jar</packaging>
    <version>${Module4.version}</version>

    <parent>
        <groupId>com.xyz</groupId>
        <artifactId>ParentProject</artifactId>
        <version>1.0</version>
    </parent>   

    <dependencies>
        <dependency>
            <groupId>com.xyz</groupId>
            <artifactId>Module1</artifactId>
            <version>${Module1.version}</version>
        </dependency>
        <dependency>
            <groupId>com.xyz</groupId>
            <artifactId>Module2</artifactId>
            <version>${Module2.version}</version>
        </dependency>
        <dependency>
            <groupId>com.xyz</groupId>
            <artifactId>Module3</artifactId>
            <version>${Module3.version}</version>
            <type>jar</type>
        </dependency>
    <dependencies>

1) At the beginning of the cycle how can I set the parent and modules to the same version in one maven command?

You no longer need to. The parent POM can remain at the same version, only changing if the parent POM changes. In which case you can use mvn version:set -DnewVersion=1.1.1. But you don't need to for this approach.

Instead you can dynamically set the version using the property main.version. E.g. mvn clean deploy -Dmain.version=1.1.1 Also, to force dynamic passing of the version number, you can leave out the default main.version property that I included in my parent POM above.

2) How do I update both mod1's version and mod2's reference to mod1's version in one step?

This basically boils down to how to manage revisions. If I do not set the revision property in the mvn command, then all the modules will use SNAPSHOT as the revision. If I set the revision property to RC1 then all the modules will get that revision. Furthermore, if I set revision to RC1 but Module4.revision to RC2 then Module4 gets RC2 and all the other modules get RC1. This fulfills the clients request to have dynamic revisions on a per module basis.

Here are some examples:

  • mvn clean deploy -Dmain.version=1.1.1 Sets all the modules to version 1.1.1-SNAPSHOT.
  • mvn clean deploy -Dmain.version=1.1.1 -Drevision=RC1 Sets all the modules to version 1.1.1-RC1.
  • mvn clean deploy -Dmain.version=1.1.1 -Drevision=RC1 -DModule4.revision=RC2 Sets all the modules to version 1.1.1-RC1 except for Module4 which is set to version 1.1.1-RC2.

There is a caveat that must be mentioned. If you increment the version of a dependent module, for example Module1, to RC2, then you must also increment the version of all the modules that use it, for example Module4 must not be incremented to RC2 (or the next version) also. This is something that the client has been made aware of and it is also why I would prefer all the modules to have the same version. But I really do like how dynamic it came out. Essentially the version is now set via command line with no updates to POM files required.

Jose Martinez
  • 9,918
  • 6
  • 41
  • 60
1

1) As @rec said, the maven release plugin would do the trick

$ mvn release:prepare -DautoVersionSubmodules=true
$ mvn release:perform

2) You can define dependency from mod2 to mod1 like:

<dependency>
    <groupId>com.xxx</groupId>
    <artifactId>mod1</artifactId>
    <version>LATEST</version>
</dependency>

More info: How do I tell Maven to use the latest version of a dependency?

I tested both solutions and it works! Hope it helps you :)

Community
  • 1
  • 1
Andrés Oviedo
  • 1,237
  • 12
  • 28