Saturday, 19 April 2014

Maven Explanation - part 3


The two earlier parts of the Maven series can be found here:
Part 1
Part 2

Up to now, we have covered Maven build lifecycle, plugin, repository and dependency management. In this part, let explore Maven module, setting, profile and IDE plugin.

Maven Modules

Parent Project 

Maven module is the part where Maven offer additional value to Ant. If you remember, Ant do not have project dependency, rather it only has task dependency. Of course, developers can use dependent tasks to execute other project build file, and through that, indirectly achieve project dependency. However, this is tedious. Moreover, if you want to child build.xml to inherit some properties, settings or even some tasks from parent build file, then it is even more difficult.

Maven take the project modules as part of its core concept and natively support it. Let start with a simple example.


We have a nested project here with the parent project named sample_maven_module. This project has 2 child projects, sub_module and web. The child project sub_module has another child project named sub_sub_module.

Below is the pom definition of the parent project:

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.blogspot.sgdevblog</groupId>
  <artifactId>sample_maven_module</artifactId>
  <packaging>pom</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>Parent pom</name>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>2.3.2</version>
          <configuration>
            <source>1.6</source>
            <target>1.6</target>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <modules>
    <module>web</module>
    <module>sub_module</module>
  </modules>
</project>

As you can see, a project can only have modules when it is packaging as pom. Parent project serve a special purpose, it is used to define common settings and declaration for child modules. If take a look at the effective pom view of child module, then we will see the parent plugins and dependencies above will be automatically inherited by the child modules.

With this feature, developers can define a common version for log4j, Spring framework, Junit or whatever plugins setting in the project.

In the above example, we have multiple layer of nested projects. If there is conflict among configurations, the configuration of nearest parent will be applied. For example, if in sub_module project, we declare

<plugin>
 <artifactId>maven-compiler-plugin</artifactId>
 <version>2.3.2</version>
 <configuration>
  <source>1.7</source>
  <target>1.7</target>
 </configuration>
</plugin>

Then, in the sub_sub_module project the source will be set to 1.7 rather than 1.6 as declared in grant parent project.

Maven module

There are two things to note about modules. Firstly, whatever mvn command trigger on parent project will be triggered by child projects as well. Secondly, Maven is smart enough to identify cross dependency among child modules and trigger the command in a proper order. However, if there is no cross dependency, the order of execution will follow declaration order.

For example, in the above project, we declare of sub_module in front of web. Normally, Maven will trigger command on sub_module, then web.

Put into practice, when we trigger mvn clean install on sample_maven_module project, here is the execution order

mvn clean install on sub_module
mvn clean install on sub_sub_module
mvn clean install on web

However, if we declare web as dependency of sub_sub_module, then sub_module will always be built first. The order will be changed as follow

mvn clean install on web
mvn clean install on sub_module
mvn clean install on sub_sub_module

It is understandable because the child module appear as dependency should be build first and Maven explore sub_sub_module only after exploring its direct parent project.

Maven Settings & Profiles

Assume that with the knowledge above, you have setup a wonderful Maven project that build like charm. Now, there are few common challenges that you may need to face if we deploy our project to other servers.

The database is set-up differently in Jenkins server and you may need another configuration for the sql plugin to populate test data. Moreover, the remote repository to deploy artifact may require authentication. For this kind of purpose, we need Maven Settings and Profiles.

For Maven newbie, the main difference between Settings and Profiles is Setting is stored in the box and Profile is part of project pom file. There are two possible places for settings.xml
  • The Maven install: $M2_HOME/conf/settings.xml
  • A user's install: ${user.home}/.m2/settings.xml
The first file contain global settings for Maven while the second file contains settings for specific user. Most of the time, we only need one.

There are reason for different mechanism of storage. Settings is considered more environment related than profile. Moreover, Settings is hidden from the projects itself.

Settings

From personal experience, I often use the Maven Settings to store information about remote repository and active profile. If you want to shorten the pom file, whatever common information about the infrastructure can be put under this file as well.

For example, only in Jenkins, you may want to generate version.txt that contain Jenkins build number and commit logs when Maven is built. Then in the pom file, declare plugin to do this task inside Jenkins profile. Then, in Jenkins settings.xml, we can activate this profile by

<settings>
  ...
  <activeProfiles>
    <activeProfile>jenkins</activeProfile>
  </activeProfiles>
  ...
</settings>

Profiles

Maven support this multiple profiles, so you can choose more than one profile to activate. There are many ways of activate a profile:

  • By jdk version
  • By OS
  • By existence of property
  • Activate by default
  • Activate by existence of file
  • Activate by specifying profile in mvn command

If a profile is not activated, all the contents inside are simply ignored by Maven.

Maven Plugins for IDE

All famous IDEs has support for Maven. However, they follow different approachs to make Maven work in IDE. With the only exceptions is Eclipse, all other IDEs let Maven run natively within the IDE. It means that any mvn command you trigger in IDE will effectively send the identical command to the console in project home. This works like you have a console open next to your IDE, just more convenient.

Eclipse Maven plugin (m2e) is much more ambitious. It tries to interpret Maven pom file to Eclipse project configuration. However, the biggest problem with this approach is the incomplete implementation. Many years after first release, the conversion of many Maven plugins to Eclipse configuration are not yet supported. It may be frustrated that Eclipse show error in pom file even the file is perfectly correct because m2e does not recognize the setting.  

Still, Eclipse approach bring some benefit, the first and foremost is workspace resolution. It helps when you build a project with multiple module. In stead of downloading dependencies from repository, m2e attempt to find any corresponding project and include this project in classpath rather than the packaged jar file. This provides immediate reflection of any API changes without going through the hassle of building dependent project.

As Eclipse settings do not fully support Maven configuration, here are some of  the common problems that you may encounter when using m2e plugin in Eclipse.

1/ Eclipse does not support dependency scope. Simply speaking, when the plugin help you to configure the classpath, it does not differentiate compile, runtime or test scope. Hence, in your IDE, no matter what scope you put, it always work but when you create deployment, only compile scope dependencies are available.

2/ Another similar issue is the lack of scope support for source folder. Most of common Maven projects includes src/main/java and src/test/java folder. However, if this project in included as dependency, Eclipse cannot differentiate the test source and main source folder. The consequence is the inclusion of test source folder into project classpath, which is not wanted.