5. Maven Lifecycle
Central to Maven is its lifecycle that provides a uniform interface for building and distributing projects. In this chapter, we will review the lifecycle and building blocks that make up the lifecycle.
Goals and Plug-ins
Build processes generating artifacts such as JAR or WAR files typically require several steps and tasks to be completed successfully in a well-defined order. Examples of such tasks include compiling source code, running unit tests, and packaging of the artifact. Maven uses the concept of goals to represent such granular tasks.
To better understand what a goal is, let’s look at an example. Listing 5-1 shows the compile goal executed on gswm project code under C:\apress\gswm-book\chapter5\gswm. As the name suggests, the compile goal compiles source code. The compile goal identifies the Java class HelloWorld.java under src/main/java, compiles it, and places the compiled class file under the target\classes folder.
C:\apress\gswm-book\chapter5\gswm>mvn compiler:compile
[INFO] Scanning for projects...
[INFO] --- maven-compiler-plugin:3.1:compile (default-cli) @ gswm ---
[INFO] Compiling 1 source file to C:\apress\gswm-book\chapter5\gswm\target\classes
[INFO] --------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] --------------------------------------------------------
Listing 5-1 Maven compile Goal
Goals in Maven are packaged in plug-ins , which are essentially a collection of one or more goals. In Listing 5-1, the compiler is the plug-in that provides the goal compile.
Listing 5-2 introduces a pretty nifty goal called clean . As mentioned earlier, the target folder holds Maven-generated temporary files and artifacts. There are times when the target folder becomes huge or when certain files that have been cached need to be cleaned out of the folder. The clean goal accomplishes exactly that, as it attempts to delete the target folder and all its contents.
C:\apress\gswm-book\chapter5\gswm>mvn clean:clean
[INFO] Scanning for projects...
[INFO] --- maven-clean-plugin:2.5:clean (default-cli) @ gswm ---
[INFO] Deleting C:\apress\gswm-book\chapter5\gswm\target
[INFO] --------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] --------------------------------------------------------
Listing 5-2 Maven clean Goal
Notice, the format of the command clean:clean in Listing 5-2. The clean before the colon (:) represents the clean plug-in, and the clean following the colon represents the clean goal. By now it should be obvious that running a goal in the command line requires the following syntax:
mvn plugin_identifier:goal_identifier
Maven provides an out-of-the box Help plug-in that can be used to list available goals in a given plug-in. Listing 5-3 shows the Help plug-in’s describe goal to display goals inside the compiler plug-in.
mvn help:describe -Dplugin=compiler
[INFO] Scanning for projects...
Name: Apache Maven Compiler Plugin
Description: The Compiler Plugin is used to compile the sources of your project.
Group Id: org.apache.maven.plugins
Artifact Id: maven-compiler-plugin
Version: 3.8.1
Goal Prefix: compiler
This plugin has 3 goals:
compiler:compile
Description: Compiles application sources
compiler:help
Description: Display help information on maven-compiler-plugin.
Call mvn compiler:help -Ddetail=true -Dgoal=<goal-name> to display parameter details.
compiler:testCompile
Description: Compiles application test sources .
Listing 5-3 Maven Help Plug-in
Plug-ins and their behavior can be configured using the plug-in section of pom.xml. Consider the case where you want to enforce that your project must be compiled with Java 8. As of version 3.8, the Maven compiler plug-in compiles the code against Java 1.6. Thus, you will need to modify the behavior of this plug-in in the pom.xml file , as shown in Listing 5-4
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Project details omitted for brevity -->
<dependencies>
<!-- Dependency details omitted for brevity -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Listing 5-4 Plug-in Element in the pom.xml File
Now if you were to run the mvn compiler:compile command, the generated class files will be of Java version 1.8.
Note
The <build /> element in pom.xml has a very useful child element called finalName. By default, the name of the Maven-generated artifact follows the <<project_artifiact_id>>-<<project_version>> format. However, sometimes you might want to change the name of the generated artifact without changing the artifactId. You can accomplish this by declaring the finalName element as <finalName>new_name</finalName>.
Lifecycle and Phases
Maven goals are granular and typically perform one task. Multiple goals need to be executed in an orderly fashion to perform complex operations such as generating artifacts or documentation. Maven simplifies these complex operations via lifecycle and phase abstractions such that build-related operations could be completed with a handful of commands.
Maven’s build lifecycle constitutes a series of stages that get executed in the same order, independent of the artifact being produced. Maven refers to the stages in a lifecycle as phases . Every Maven project has the following three built-in lifecycles:
-
default: This lifecycle handles the compiling, packaging, and deployment of a Maven project.
-
clean: This lifecycle handles the deletion of temporary files and generated artifacts from the target directory.
-
site: This lifecycle handles the generation of documentation and site generation.
To better understand the build lifecycle and its phases, let’s look at some of the phases associated with the default lifecycle:
-
validate: Runs checks to ensure that the project is correct and that all dependencies are downloaded and available.
-
compile: Compiles the source code.
-
test: Runs unit tests using frameworks. This step doesn’t require that the application be packaged.
-
package: Assembles compiled code into a distributable format, such as
JARorWAR. -
install: Installs the packaged archive into a local repository. The archive is now available for use by any project running on that machine.
-
deploy: Pushes the built archive into a remote repository for use by other teams and team members.
Maven lifecycle is an abstract concept and can’t be directly executed. Instead, you execute one or more phases. For example, the command mvn package will execute the package phase of the default lifecycle. In addition to clearly defining the ordering of phases in a lifecycle, Maven also automatically executes all the phases prior to a requested phase. So, when the mvn package command is run, Maven will run all prior phases such as compile and test.
Life cycle -> phase -> goal
A number of tasks need to be performed in each phase. For that to happen, each phase is associated with zero or more goals. The phase simply delegates those tasks to its associated goals. Figure 5-1 shows the association between lifecycle, phases, goals, and plug-ins.
Figure 5-1 Association between lifecycle, phases, goals, and plug-ins
It is valid for a Maven phase to not have any goals associated with it. In that case, Maven will skip the phase execution. Such phases serve as placeholders for users and third-party vendors to associate their custom-built goals.
The <packaging /> element in the pom.xml file will automatically assign the right goals for each of the phases without any additional configuration. Remember that this is a benefit of CoC. For example, if the packaging element is jar, then the package phase will be bound to the jar goal in the jar plug-in. Similarly, for a WAR artifact, pom.xml will bind the package to a war goal in the war plug-in. Figure 5-2 shows a portion of the internal lifecycle associated with a WAR project.
Figure 5-2 Default lifecycle for WAR project
Skipping Tests
As discussed earlier, when you run the package phase, the test phase is also run and all of the unit tests get executed. If there are any failures in the test phase, the build fails. This is the desired behavior. However, there are times, for example, when dealing with a legacy project, where you would like to skip compiling and running the tests so you can build a project successfully. You can achieve this using the maven.test.skip property . Here is an example of using this property:
mvn package –Dmaven.test.skip=true
Plug-in Development
Developing custom plug-ins for Maven is very straightforward. As discussed earlier, a plug-in is simply a collection of goals. Thus, when we talk about plug-in development, we are essentially talking about developing goals. In Java, these goals are implemented using MOJOs, which stands for Maven Old Java Object, and it is similar to Java’s Plain Old Java Object (POJO).
This section explains how to develop a SystemInfoPlugin that displays system information such as Java version, operating system, and the like, on the console running Maven command.
Let’s start this plug-in development by creating a Maven Java project, named gswm-maven-plugin, as shown in Figure 5-3
Figure 5-3 Maven project for plug-in development
Note: In this chapter, we are manually creating the plug-in project. Maven provides a
mavan-archetype-mojo, which would jumpstart your plug-in development. We will learn about Maven archetypes in Chapter 6
The content of the pom.xml file is shown in Listing 5-5. Notice that the packaging type is maven-plugin. We added the maven-plugin-api and maven-plugin-annotations dependencies, because they are needed for plug-in development. We will be leveraging Apache Commons Lang to get system information. Hence, we have also added the Apache Commons Lang 3 dependency.
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.plugins</groupId>
<artifactId>gswm-maven-plugin</artifactId>
<version>1.0.0</version>
<packaging>maven-plugin</packaging>
<description>System Info Plugin</description>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
<!-- Use the latest version of Plugin -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.0</version>
</plugin>
</plugins>
</build>
</project>
Listing 5-5 The pom.xml with Dependencies
The next step in the development process is creating the MOJO. Listing 5-6 shows the code for SystemInfoMojo. The @Mojo annotation marks the SystemInfoMojo class as a Mojo with “systeminfo” as the goal name. The execute method contains that goal logic. In SystemInfoMojo, we simply log several pieces of system information to the console.
package com.apress.plugins;
import org.apache.commons.lang3.SystemUtils;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
@Mojo( name = "systeminfo")
public class SystemInfoMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info( "Java Home: " + SystemUtils.JAVA_HOME );
getLog().info( "Java Version: "+ SystemUtils.JAVA_VERSION);
getLog().info( "OS Name: " + SystemUtils.OS_NAME );
getLog().info( "OS Version: " + SystemUtils.OS_VERSION );
getLog().info( "User Name: " + SystemUtils.USER_NAME );
}
}
Listing 5-6 SystemInfoMojo Java Class
The final step in this process is installing the plug-in in the Maven repository. Run the mvn install command at the root of the directory and you should get the output shown in Listing 5-7
C:\apress\gswm-book\chapter5\gswm-maven-plugin>mvn install
[INFO] Scanning for projects...
[INFO]
[INFO] --------< com.apress.plugins:gswm-maven-plugin >--------
[INFO] Building gswm-maven-plugin 1.0.0
[INFO] -------------------[ maven-plugin ]---------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ gswm-maven-plugin
[INFO] java-annotations mojo extractor found 1 mojo descriptor.
[INFO] --- maven-install-plugin:2.4:install (default-install) @ gswm-maven-plugin ---
[INFO] Installing C:\apress\gswm-book\chapter5\gswm-maven-plugin\target\gswm-maven-plugin-1.0.0.jar to C:\Users\<<USER_NAME>>\.m2\repository\com\apress\plugins\gswm-plugin\1.0.0\gswm-maven-plugin-1.0.0.jar
[INFO] Installing C:\apress\gswm-book\chapter5\gswm-maven-plugin\pom.xml to C:\Users\<<USER_NAME>>\.m2\repository\com\apress\plugins\gswm-maven-plugin\1.0.0\gswm-maven-plugin-1.0.0.pom
[INFO] --------------------------------------------------------
[INFO] BUILD SUCCESS
Listing 5-7 Maven install Command
Now you’re ready to start using this plug-in. Remember that the syntax to run any goal is mvn pluginId:goal-name. Listing 5-8 shows this plug-in in action. Notice system information displayed on the console.
C:\apress\gswm-book\chapter5\gswm-plugin>mvn com.apress.plugins:gswm-maven-plugin:systeminfo
[INFO] Scanning for projects...
[INFO] --- gswm-maven-plugin:1.0.0:systeminfo (default-cli) @ gswm-maven-plugin ---
[INFO] Java Home: C:\java\jdk-11
[INFO] Java Version: 11.0.1
[INFO] OS Name: Windows
[INFO] OS Version: 10
[INFO] User Name: Balaji
[INFO] --------------------------------------------------------
Listing 5-8 Running the SystemInfoMojo Plug-in
The newly developed plug-in is also ready to be used in other Maven projects. Listing 5-9 shows a portion of the POM file that attaches systeminfo goal to the validate phase.
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.apress.plugins</groupId>
<artifactId>gswm-plugin-test</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<description>Plugin Test</description>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies />
<build>
<plugins>
<plugin>
<groupId>com.apress.plugins</groupId>
<artifactId>gswm-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>systeminfo</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Listing 5-9 POM File Using systeminfo Goal
When the Maven phase such as compile or package is invoked, you will see the output of the systeminfo goal as shown in Listing 5-10.
mvn compile
[INFO] Scanning for projects...
[INFO] Building gswm-plugin-test 1.0.0
[INFO] Java Home: C:\java\jdk-11
[INFO] Java Version: 11.0.1
[INFO] OS Name: Windows
[INFO] OS Version: 10
[INFO] User Name: Balaji
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ gswm-plugin-test ---
Listing 5-10 Compile Phase Output
Summary
Maven uses plug-in-based architecture that allows its functionality to be extended easily. Each plug-in is a collection of one or more goals that can be used to execute tasks, such as compiling source code or running tests. Maven ties goals to phases. Phases are typically executed in a sequence as part of a build lifecycle. You also learned the basics of creating a plug-in.
In the next chapter, you will be introduced to archetypes and learn about multimodule projects.