Saturday, August 20, 2011

Customizing Maven's lifecycle

Every project has a lifecycle from project initiation all the way through regular production releases. A typical lifecycle involves at least the basic steps of getting all the resources together, compilation of source-code, unit-tests, integration-tests, packaging to create the custom-format. Maven formalizes this concept into a “default” lifecycle that every project is expected to have.

According to Maven, a lifecycle is made up of a sequence of phases. Each phase has zero or more plugin goals bound to it. Some of the core plugin goals are bound to the core phase steps by default, e.g the compiler plugin’s compile goal is bound to the compile phase. A more clear second example would be the Surefire plugin’s test goal is bound to the unit test phase. However, some phases are free-form and not necessary part of the core of the default lifecycle, they will be activated only when a corresponding plugin goal bound to that phase by default is configured. At the same time, some plugins and their goals are free-form and it is upto the developer to bind them to a particular phase.

Maven site lists all the following goals as part of the default lifecycle for any project regardless of the packaging type. Here are a few examples on what aspects of the project build we can customize by leveraging what plugins.

1.validate - Validates the project is correct and all necessary information is available. The enforcer plugin can be used here to enforce a few environment-specific constraints on the project.
Eg.
            
                org.apache.maven.plugins
                maven-enforcer-plugin
                
                    
                        enforce-versions
                        
                            enforce
                        
                        
                            
                                
                                    2.0.10
                                
                                
                                    1.6
                                
                            
                        
                    
                
            
          
          
2. initialize - Initialize build state, e.g. set properties or create directories. An example would be to use some programmatic plugins like antrun or groovy to generate custom properties. In the example below, the property ‘buildtime’ was created during build-time and it will be used inside the manifest file to mark the time the jar/war/ear was created.
            
                org.codehaus.mojo
                groovy-maven-plugin
                1.3
                
                    
                        generate-resources
                        
                            execute
                        
                        
                            
                                import java.util.Date
                                import java.text.MessageFormat
                                def buildtime = MessageFormat.format("{yyyy-MM-dd HH:mm:sss}", new Date())
                                project.properties['buildtime'] = buildtime
                            
3.generate-sources - Generate any source code for inclusion in compilation. Projects that use webservices or any of the Java-XML bindings, like JAXB2, XMLBeans, Castor can make use of the corresponding plugins (e.g. axistools:wsdl2java, jaxb2:xjc, xmlbeans:xmlbeans, castor:castor) to generate any source code in this phase. These plugin goals bind by default to this lifecycle phase so that you do not have to explicitly mention it.
      

            
                org.codehaus.mojo
                jaxb2-maven-plugin
                1.3.1
                
                    
                        xjc
                        
                            xjc
                        
                    
               
               
                    com.example.myschema 
                
4. process-sources - Process the source code, for example to filter any values from within the source code. More on filtering in a later blog.
5. generate-resources - Generate resources for inclusion in the package like WSDLs in a web-services project (e.g  axistools:java2wsdl), or Hibernate Configuration XML or hbm.xmls (hibernate3:hbm2cfgxml)
6. process-resources - Copy and process the resources into the destination directory, ready for packaging. This is also a great place to filter any values from within the resources file. More on filtering in a later blog.
7. compile    - This is the most obvious phase. Although the compiler plugin is part of the core lifecycle and need not be defined explicitly to be attached to any phase, there is often a need to customize the JDK version to use. Eg
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    1.6
                    1.6
                    true
                
            

          
8. process-classes- This phase is used to post-process the generated class files from compilation, for example to do bytecode enhancement on Java classes.
9. generate-test-sources - This is conceptually same as generate-sources phase as above, except that we are generating test source code here.
10. process-test-sources - This phase is used to process test source files, e.g to filter any values, make program variables point to a test DB location instead of production DB location etc.
            
                org.apache.maven.plugins
                maven-antrun-plugin
                
                    
                        update-test-sources
                        process-test-sources
                        
                            run
                        
                        
                            
                                
                                    [test-DB-path-placeholder]
                                    ${testDB.path} // testDB.path is a locally defined property.
                                
                            
                        
                    
                
            

11. generate-test-resources    - This phase is used to create resources for testing. E.g. Let's say your project's tests are off some companydata.dat file which is a part of the companycore.jar. Since your project is interested only in this data file for the purposes of testing, you could use maven-dependency-plugin to unpack the companycore.jar, extracting only that file into your test-resources directory.
            
                org.apache.maven.plugins
                maven-dependency-plugin
                
                    
                        unpack-test-resources
                        generate-test-resources
                        
                            unpack
                        
                        
                            
                                
                                    com.company.core
                                    core-artifacts
                                    1.0
                                    test-jar
                                    true
                                    companydata.dat
                                
                            
                            ${build.testResourcesDir}
                        
                    
                
            

12. process-test-resources - This phase is used to copy and process the resources into the test destination directory.
13. test-compile - The compiler plugin compiles the test source code into the test destination directory.
14. process-test-classes - Conceptually same as process-classes to do any byte code enhancement.
15. test - The Surefire plugin runs tests using a suitable unit testing framework. You may need to configure the plugin to skipTests or exclude any particular test class that are causing the build to fail. This may be useful during active development phase.
            
                org.apache.maven.plugins
                maven-surefire-plugin
                
                    
                        **/TestFeatureX.java
 

16. package   - Take the compiled code and package it in its distributable format, such as a JAR. You can request Maven to package source files and test source files into respective jars via maven-jar-plugin and maven-source-plugin in addition to the distributable class file jar that it creates.
            
                org.apache.maven.plugins
                maven-jar-plugin
                
                    
                        
                            test-jar
                        
                    
                
            
            
                org.apache.maven.plugins
                maven-source-plugin
                
                    
                        attach-sources
                        package
                        
                            jar
                        
                    
                
            
            
                org.apache.maven.plugins
                maven-war-plugin
                
                    
                        false
                        
                            true
                        
                        
                            ${timestamp}

17. verify- This phase can be used to run any checks to verify the package is valid and meets quality criteria. It is a great place to enforce any source code formatting styles.
            
                org.apache.maven.plugins
                maven-checkstyle-plugin
                
                    
                        verify
                        
                            checkstyle
                        
                    
                
                
                    path_to/checkstyle_rules.xml
                    true
                    true
 


So the above list gives a fair idea on how you can leverage Maven plugins to customize your build lifecycle. The above are just brief samples. Any ideas beyond the above usage of plugins in various build phases is more than welcome.

5 comments:

Javin Paul said...

fanstic post man, a must read for anyone who is learning Maven, bookmarked it.

Thanks
Javin
How HashMap works in Java

intangible said...

I wouldn't mind some more info on how maven expects the integration tests part of the lifecycle to work... Especially in regards to web-apps...
Most of the time I find myself just using "jetty:run" to test things, but I'm sure maven probably has a better way.

Chris Wilkes said...

Example under 7 compile looks like it is missing the source tags.
Thanks for putting this up, great resource for finding out what phase to put something in.

pragmaticjava said...

@intangible - I have not worked with jetty:run. My guess is that you can bind the jetty:run goal to the integration phase of the lifecycle. See http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html as a reference to where integration phase lies in the entire lifecycle.

pragmaticjava said...

@Chris Wilkes - Thanks for pointing out the typo.
I am glad that this post is useful to you.