Sunday, November 25, 2012

Maven World


In software development process, one of the most painful part is the initial project configuration and generating stable builds. This cannot be more true when the project relies on other dependent services and custom API's. Managing the builds along with their version info relevant to the source management system would be difficult. The problem becomes a nutcracker when multiple teams working on different components of the project simultaneously, update their code and have their build tested locally with no distribution management and central build standardization in place. It certainly becomes frustrating when after working with the API for a while, you find out its being modified in the mean time. Also generating a project build itself has become a complex process with increasing number of dependent components, external resources, configuration and environments etc. Although build tools such as ANT does handles the problem of build automation, it fails to address the issues surrounding the build distribution, versioning and standardization. Although ANT tasks do name the build version and copies it to the build location which can later be maintained by the source control itself, it does fall short to solve some of the critical problems. It fails to have a any formal conventions such as standard directory structure and does not have lifecycle specified for the tasks which execute procedurally.
   Maven since its emergence has been targeted to encounter all the above issues and provide a reliable project management tool along with build management capabilities. It provides a reliable dependency management system with project lifecycle management and drives on project object model. Maven performs all its operations including common build tasks using its plugins shared and maintained in a central repository. The plugins use the data provided in the pom as well as configuration parameters to carry out the task. Maven maintains a model of the project along with its dependencies and plugins required from the repository thus promoting reusability of build logic. The project model defines a unique set of coordinates consisting of a group identifier, an artifact identifier, and a version, providing the coordinates to declare dependencies. These co-ordinates are used to create repositories of maven artifacts and with the help of tools such as Nexus and Artifactory can be accessed remotely as well. These remote repositories usually mirror the maven central repository.
    Installing maven is quick and easy, involving downloading of maven binary files, extracting in a directory, adding M2_HOME system variable with maven executable path, appending maven executable path to the PATH system variable and add MAVEN_OPTS system variable providing JVM execution parameters (-Xmx1024m -Xms512m -XX:MaxPermSize=512m). Normally the maven settings file is setup in the user profile directory (under ~/.m2/repository/ folder) or in conf directory of maven home/installation path. The settings.xml mainly provides the urls for the remote repository. The location of the setting.xml can be overridden in the command line using the --settings or --s option as below:
mvn --settings ~/.m2/settings-customer1.xml clean install
mvn –s ~/.m2/settings-customer1.xml clean install

A maven project has a standard structure inside the ${basedir} directory which represents the directory containing pom.xml. The source code lies in the ${basedir}/src/main/java, resources in the ${basedir}/src/main/resources, tests in ${basedir}/src/test and byte code in ${basedir}/target directory.


Project Object Model (POM)
It contains the groupId, artifactId, packaging and version info which form the co-ordinates to uniquely identify the project and define relationships between other projects through dependencies, parents and prerequisites. The name and url are descriptive elements useful for maven site generation. The dependency element defines the co-ordinates for dependent project/plugins and provides the scope attribute to limit the transitivity of the dependency. Following are 6 scopes available:
  • compile: It is default scope and makes dependencies available in all classpaths of a project and propagated over to dependent projects.
  • provided:  It indicates that the JDK or a container is expected to provide the dependency at runtime.
  • runtime: It indicates that the dependency is not required for compilation, but is for execution.
  • test: It indicates that the dependency is only available for test compilation and execution phases.
  • system: It is similar to provided except needs to provide the JAR which contains dependency explicitly
  • import: It indicates that the specified POM should be replaced with the dependencies in that POM's <dependencyManagement> section.
    The dependency section could also have a classifier attribute which distinguishes between the artifacts built from the same POM but differ in the either java version or build type (jar/ear etc) . Also if case some of the transitive dependencies referred by the dependency are not needed, they can be excluded simply using the exclusions section targeted at a specific groupId and artifactId..
Dependencies can be specified directly in the POM using the dependencies element or in the dependencyManagement section. The Dependency management section allows to consolidate and centralize the management of dependency versions without adding dependencies which are inherited by all children, useful especially for a set of projects with a common parent.

Maven executes against the effective POM which is the combination of the project's POM, all parent POMs, maven super-POM and user defined settings and active profiles. All the maven projects ultimately extend the super-POM, which defines a set of sensible default configuration settings. The version dependencies in pom can be overridden from super-pom to settings.xml to parent POM to child POMs. Following command is used to see the such effective POM:
  mvn help:effective-pom

Maven carries out all its operations using plugins as mentioned before. A maven plugin is considered as a collection of one or more goals. A goal on the other hand is a unit of work, a specific task executed either as standalone or together with other goals (executed as pluginId:goalId). For example a compile goal has a standalone compile plugin to compile source code, and surefire plugin contains goals for executing tests and generating reports. Goals can be configured via configuration properties to customize the behavior and also define parameters with default values. Goals are executed in context of the POM which defines the project. Besides executing tasks as goals, maven allows to define a lifecycle phase. A phase is a single step in the maven build lifecycle which has an ordered sequence of phases in order to build the project. Plugin goals can be attached to the lifecycle phase, which can have zero or multiple goals. When maven moves through the phases in a lifecycle, it executes the goals attached to each particular phase. Maven can support a number of custom lifecycles, but the default lifecycle is predominantly used. Execution of a phase will first execute all preceding phases in order, ending with the phase specified on the command line.
    A maven repository constitutes a collection of project artifacts stored in a directory structure which matches closely with the maven co-ordinates. Maven looks up for the artifact in the local repository first and if not found tries to load it from the remote repository. Maven downloads all the  POM files for dependency with the artifacts on order to support transitive dependencies. Transitive dependencies are the dependencies declared in the POM file on other artifacts. Maven adds all the dependencies of the library to the project’s dependencies thus implicitly resolving conflicts with its default behavior. Maven uses the dependencies already present in the local repository, built from a local project (or loaded from remote repository) even if the same local project fails to build currently. Also if a maven-dependency-project in the middle of the maven dependency chain is rebuild and loaded in the local or remote repository, there is no need to build all the other dependency projects dependent on such newly built dependency-project.

Imagine a project divided into multiple components, with each component dependent on the other to compile itself such as the traditional 3 tier architecture. Multi-module project type in maven can be used to tackle this issue. A multi-module project doesn't produce any new artifact and is composed of several other projects known as modules. In a multi-module project, maven propagates all the commands made on the project, to be executed on its child projects by automatically discovering the correct execution order and detecting the circular dependencies. The multi-module project is setup as follows:

1) Create a new project directory containing all the child projects.
2) Create a new pom file with new artifactId and packaging as "pom".
3) Declare a modules section in the POM file with the child modules for each child project located in the sub-directories.

   simple-weather
   simple-webapp

  It is vital to specify the repository info in the pom.xml in order to download all the dependent jars and maven plugins. The repositories section in the pom is used for the same purpose and list all the available repositories using the repository elements. The distribution management section on the other hand also specifies the repository info, but it is only used for distribution of the artifact and supporting files generated throughout the build process. The pluginRepositories section similar to the repositories section or element, specifies a remote location were maven could find new plugins using the pluginRepository elements. A continuous integration management section using ciManagement element enables to specify the build system being used by the project and the URL for the job. Also a notifiers settings can be specified to configure email address to trigger emails based on build status. A source code management section can be used to provide information regarding the version control system for the project as in the example below. The connection parameter requires read access for maven to access the source code, while the developerconnection requires write access to access the source code. The url parameter specifies the view to browse the repository.
  
  https://mercurial.local.com/repo/lmo/project-repo/
  scm:hg:ssh://hg@mercurial.local.com//mercurial/lmo/project-repo/
  scm:hg:ssh://hg@mercurial.local.com//mercurial/lmo/project-repo/


Maven ensures that the builds are portable with the build configuration in the POM.xml avoiding references to the local file system and depending more on the metadata from the repository. But there are circumstances when the build configuration requires slight changes in the dependency, or path to the local file system or extra steps in the its configuration making a single build configuration impossible to work for different environments. To handle such case, maven introduced a concept of build profile. The profile consists of configuration for a subset of elements in the POM which modify the POM during the build time giving different build results based on the environment. The -f option allows to create a another POM based on the build parameters, inorder to make the build configuration more maintainable. Profiles can also be specified in the maven settings.xml file to configure different repositories for example. Profiles in the settings.xml can be activated in the Maven settings, via the <activeProfiles> section which takes a list of <activeProfile> elements, each containing a profile-id inside. Profiles listed in the <activeProfiles> tag would be activated by default every time a project uses it. Below is a sample profile section in the settings.xml:
<profiles>
 <profile>
  <id>ext-plugins</id>
  <pluginRepositories>
   <pluginRepository>
    <id>extPlugins-releases</id>
    <url>http://dx.server.com:8080/nexus/content/repositories/releases
    </url>
   </pluginRepository>
   <pluginRepository>
    <id>extPlugins-snapshot</id>
    <url>http://dx.server.com:8080/nexus/content/repositories/snapshots
    </url>
   </pluginRepository>
  </pluginRepositories>
 </profile>
</profiles>

<activeProfiles>
 <activeProfile>ext-plugins</activeProfile>
</activeProfiles>

Maven also supports running Ant tasks or targets embedded in the POM using the Maven Antrun Plugin. It helps in easy migration from Ant scripts to Maven and also provides a way to execute custom commands in the build step. In order to execute the 'run' goal in maven-antrun plugin, it needs to be binded to the 'validate' phase of the maven lifecycle. Also to execute conditional ant tasks such as 'if' or 'equals', a reference to antcontrib is required. Hence we include a task definition with the resource "net/sf/antcontrib/antlib.xml" added to the "maven.plugin.classpath". Further ant-contrib and ant-nodeps dependencies are added in the plugins section as follows.
<build>
 <plugins>
 <plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.6</version>
  <executions>
   <execution>
    <id>prepare</id>
    <phase>validate</phase>
    <configuration>
     <tasks>
      <taskdef resource="net/sf/antcontrib/antlib.xml" classpathref="maven.plugin.classpath" />
      <propertyregex property="ear.artifactId" input="${artifactId}" regexp="-web$" 
          replace="-ear" global="true" defaultValue="${artifactId}" />
      <echo message="checking ${site.root}\${artifactId}\${buildenv}\*.zip exists" />
      <fileset dir="${site.root}\${artifactId}\${buildenv}" includes="**/*.zip" id="checkdir"/> 
      <if>
       <equals arg1="${toString:checkdir}" arg2="" /> 
<!--       <available file="${site.root}\${artifactId}\${buildenv}\*.zip" />  -->
       <then>
        <echo>Zip file does not exists</echo>
       </then>
       <else>
        <echo message="extracting ${site.root}\${artifactId}\${buildenv}\*.zip" />
        <unzip dest="${site.root}\${artifactId}\${buildenv}\">
         <fileset dir="${site.root}\${artifactId}\${buildenv}" includes="**/*.zip" />
        </unzip> 
        <delete>
         <fileset dir="${site.root}\${artifactId}\${buildenv}" includes="**/*.zip"/>
        </delete>
       </else>
      </if>
     </tasks>
    </configuration>
    <goals>
     <goal>run</goal>
    </goals>
   </execution>
  </executions>
  <dependencies>
    <dependency>
      <groupId>ant-contrib</groupId>
      <artifactId>ant-contrib</artifactId>
      <version>1.0b3</version>
      <exclusions>
         <exclusion>
           <groupId>ant</groupId>
           <artifactId>ant</artifactId>
         </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.apache.ant</groupId>
      <artifactId>ant-nodeps</artifactId>
      <version>1.8.1</version>
    </dependency>
  </dependencies>
 </plugin>
 </plugins>
</build>

Reporting Section
Maven provides a reporting section (element) which allows to include additional reporting plugins for maven site generation. The maven site contains the current status details of the project, such as dependencies information, module information, java docs etc. Reports can be ran separately on individual modules or can be aggregated in case of aggregator reports by defining reportSets for plugins. Also inherited element specifies whether the report plugin should be applied to child projects POMs which is inherited from the current project. By default the value of inheriting the plugin configuration is true. Although such option in not available for the build plugins.

Running Maven in Eclipse
In order to run maven commands in Eclipse as an external tool follow the below steps:
  1. Install maven (preferably maven 3.0) on the machine and locate its path (C:\Program file\..).
  2. In eclipse, from the menu bar, select Window > Preferences. Select the Run/Debug (expand it) > String Substitution. Add a new variable e.g. maven_exec and select the file "mvn.bat" for value using the installed maven location.
  3. Set up a new external launcher from the menu bar, select Run > External Tools > External Tool Configurations. Then select Program from the menu.
  4. Create each maven task, for example: the task for build classpath,
    1. On program, create a new program and name as: build_classpath (any name as you wish) 
    2. In location box: choose our created variable: maven_exec 
    3. In Working Directory: choose variable: ${workspace_loc}
    4. In Argument, give maven command, in here is: eclipse:clean eclipse:eclipse 
    5. Click on Apply and Run
  5. So one can execute the external tool program by execute Run button, and the result should be in console tab.


Maven Commands:

1) Following are some of the most used maven command-line options:

-cpu : Check for plugin updates
-D    : Define a system property
-e     : Display execution error messages
-f     : Force to use alternate POM file.
-fae  : Only fail the build at the end, allow other builds to continue
-ff    : Stop at first failure in the build
-fn   : Never fail the build regardless of the project result (mostly used when test fails in local build)
-N    : Do not recurse into sub projects
-npu : Do not check for updates of any plugin releases
-o     : Work in offline mode (used to avoid getting new updates, sandboxing local development)
-rf    : Resume from the specified project
-U    : Forced to check for updated snapshots and releases on remote repository
-up   : Similar to -cpu, updates plugins
-X    : Produce debug execution output.


2)  The archetype creation goal looks for an archetype with a given groupId, artifactId, and version and retrieves it from the remote repository. It is then processed against a set of user parameters to create a working Maven project. (Deprecated):
mvn archetype:create  -DgroupId=org.apache.maven.plugin.my  -DartifactId=maven-my-plugin  -DarchetypeArtifactId=maven-archetype-mojo

3)  Generates a new project from an archetype in a directory corresponding to its artifactId, or updates the actual project if using a partial archetype in the current directory:
mvn archetype:generate -DgroupId=org.apache.maven.plugins -DartifactId=link-globals -DpackageName=org.apache.maven.plugins.http -Dversion=1.0-SNAPSHOT

Choose a number or apply filter: ... : 233: 2
Choose a number: 2: 2

4)  Generates eclipse configuration files such as .project and .classpath files, .setting/org.eclipse.jdt.core.prefs file with project specific compiler settings, and various configuration files for WTP (Web Tools Project) is wtpversion is specified:
mvn eclipse:eclipse

Note: If we didn't make any typos in group/artifactId's Eclipse should be able to resolve the dependencies; this is because Eclipse has something called workspace resolution which should be turned on by default. The workspace resolution basically means 'look in the project first, and then look in the Maven repository'. This mechanism allows to edit modules and have the changes immediately visible in other modules (including dependency updates), without having to do a mvn clean install first to get the updated module into your local m2 repository. The versions have to match up however, so if we want changes to be visible, we should refer to the latest -SNAPSHOT release.

The --resume-from option alllows to continue from the speicifed module if there is any failure in prior modules.
mvn eclipse:eclipse --resume-from module-c.war

The eclipse plugin creates subprojects for the dependencies which exists in the reactor. In case working with the deployed packages is preferred over deveopment code, useProjectPreferences is set to false as below:
mvn eclipse:eclipse -Declipse.useProjectReferences=false

5) Deletes the .project, .classpath, .wtpmodules files and .settings folder used by Eclipse:
mvn eclipse:clean

6) Adds the classpath variable M2_REPO to eclipse in order to recognize the dependencies (eclipse:add-maven-repo which also did the same is currently deprecated):
mvn eclipse:configure-workspace -Declipse.workspace=<path to the workspace> 

mvn eclipse:add-maven-repo -Declipse.workspace=<path to the workspace>

7) Skips the execution of tests for a particular project across all modules. It is a property defined in Maven Surefire plugin.
mvn -DskipTests=true
mvn -DskipTests

8)  Skips the compilation of the unit tests. The maven.test.skip properties works along with Surefire Failsafe and the Compiler Plugin to skip compilation of all tests:
mvn install -Dmaven.test.skip=true
mvn install -Dmaven.test.skip

9)  Installs an artifact into local repository and skips the execution of integration tests
mvn -DskipITs=true install

10)  Runs all the integration tests and also builds a package
mvn verify

11) To continue and build a project even when the Surefire plugin encounters failed test cases:
mvn test -Dmaven.test.failure.ignore=true

12) Executes all the integration tests which are wired using the Failsafe plugin:
mvn integration-test

13)  Delete all the classes, compile skipping all tests and if failure resume from specified project:
mvn clean install --resume-from mamos-services-web -Dmaven.test.skip=true

14) Makes sure it gets latest snapshot from the server:
mvn -U install

15) Dependency tree lists all dependencies with child dependencies. Dependency resolve finds all the resolved dependencies from the repository (showing the latest available release versions):
mvn dependency:tree >%temp%\dep.txt
mvn dependency:resolve

16)  Executes the java main class using the Exec plugin from Codehaus mojo project:
mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main

17)  Assembles an application bundle or distribution from an assembly descriptor in an archive format, by grouping the files, directories, and dependencies.
mvn install assembly:single

18)  Adds the manually downloaded jar to the maven local (install:install-file) and remote  (deploy:deploy-file) repository respectively with the specified groupId and artifactId.
mvn install:install-file -Dfile=rally-rest-api-1.0.6.jar -DgroupId=rally-rest-api -DartifactId=rally-rest-api -Dversion=1.0.6 -Dpackaging=jar

mvn deploy:deploy-file -Dfile=rally-rest-api-1.0.6.jar -DgroupId=com.rallydev -DartifactId=rally-rest-api -Dversion=1.0.6 -Dpackaging=jar -Durl=http://reposerver.com/nexus/content/repositories/thirdparty/

19)  Deletes or purges the specified maven groupId from the local repository. If no manualInclude is specified then deletes all the contents of the repository. This helps to remove the old dependencies loaded in local repository which may not be present in the central repository avoiding build related issues.
mvn org.apache.maven.plugins:maven-dependency-plugin:2.6:purge-local-repository -DmanualInclude=org.springframework

20)  Maven release-plugin is used to release the project and increment the development version. It performs the project release operation in three steps, prepare, release and clean.
  The release:prepare step removes all the SNAPSHOT versions from the POMs by default, run the tests, commit the POMs, tag the release version, increments the SNAPSHOT pom version and commits the modified POM's. It provides many options to specify release version details. The 'releaseVersion' and 'developmentVersion' parameters can be used to determine the release and development version respectively. Otherwise the user will be prompted for these values. The 'ignoreSnapshots' option avoids removing all the SNAPSHOT versions. Also specifying 'updateDependencies' prevents from updating the dependencies version to the next development version. The 'pushChanges' parameter is implemented for Git and determines whether the changes should be pushed to the remote repository. The 'scmCommentPrefix' is used to add a customized message/comment while pushing the changes.
  The release:perform step checks out from an SCM url and creates the build using deploy maven goal. In case required to rollback, the release:rollback step can be executed if clean release is not executed. It reverts all the POM changes and removes the release tag from the SCM.  Finally the release:clean step deletes the release descriptor and all the backup POM files.

mvn release:prepare -DreleaseVersion=1.0 -DdevelopmentVersion=1.1-SNAPSHOT -DignoreSnapshots -DupdateDependencies=false
mvn release:perform
mvn release:clean

In case we don't want to push the release plugin changes to the source control, the pushChanges parameter can be passed as false.
mvn release:prepare -DpushChanges=false

21)  Increase the number of concurrent download threads in order to download maven dependencies, thus speeding up the build process especially when building for the first time.
mvn -Dmaven.artifact.threads=4

22)  Managing Failures: Maven proposes three different ways for managing failures in reactor builds; fail-fast (default), fail-at-end and fail-never.
  1. Fail Fast policy stops the reactor build after the first failing module or project. Despite used by default, it can be enabled using: --fail-fast or -ff parameter.
  2. Fail At End policy fails the build afterward and allows all non-impacted builds to continue. To enable this policy, use the --fail-at-end or -fae parameter. This option avoid issue propagation and the global build is also considered as failed.
  3. Fail Never policy never fails the build, regardless of the project result. All the failures are ignore, the build just continues. It can be enabled by using --fail-never or -fn parameter.
mvn clean install --fail-at-end
mvn clean install --fail-never

23)  Maven help plugin enables to describe the goals i.e. the attributes of the specified plugin or Mojo.
mvn help:describe -Dplugin=pluginname

24)  Compile the project and execute the specified main class using the '-exec.mainClass' parameter.
mvn compile exec:java -Dexec.mainClass=com.company.application.Test

25)  Maven dependency plugin copies the dependencies from the repository to a defined location (by default the application target directory).
mvn dependency:copy-dependencies

26)  Download (resolve) the source code and java docs for each of the dependencies in the pom file.
mvn dependency:sources
mvn dependency:resolve -Dclassifier=javadoc

No comments: