Jade Dungeon

自制Maven插件

自制Maven插件

参看maven/maven.in.action/ch-17的代码

一般步骤:

  • mvn archetype:generate,然后选择maven-archtype-plugin (An archtype which contains a sample Maven plugin.)
  • 编写插件的目标,称为Mojo类,继承自AbstractMojo
  • 提供配置点。
  • 实现目标的行为。
  • 定义错误及异常时Maven的行为。
  • 测试

以下用一个统计代码行数的插件作为例子。

创建项目

mvn archetype:generate

然后选择:

maven-archtype-plugin (An archtype which contains a sample Maven plugin.)

生成pom.xml

  • packaging必须为maven-plugin
  • 依赖maven-plugin-api版本要和maven版本保持一致。
<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/maven-v4_0_0.xsd">
	
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.juvenxu.mvnbook</groupId>
	<artifactId>maven-loc-plugin</artifactId>
	<packaging>maven-plugin</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>Maven LOC Plugin</name>
	<url>http://www.juvenxu.com/</url>

	<properties>
		<maven.version>3.0-beta-1</maven.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-plugin-api</artifactId>
			<version>${maven.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-model</artifactId>
			<version>${maven.version}</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

编写目标

Archetype生成的会有一个默认的MyMojo.java,删除掉它生成一个自己的CountMojo

  • 继承AbstractMojo,类上注释@goal count表示这是count目标
  • @parameter表示是可以配置的参数
  • execute()方法里是行为。
/**
 * Goal which counts lines of code of a project
 * 
 * @goal count
 */
public class CountMojo extends AbstractMojo {

	private static final String[] INCLUDES_DEFAULT = { "java", "xml", "properties" };

	/**
	 * @parameter expression="${project.basedir}"
	 * @required
	 * @readonly
	 */
	private File basedir;

	/**
	 * @parameter expression="${project.build.sourceDirectory}"
	 * @required
	 * @readonly
	 */
	private File sourceDirectory;

	/**
	 * @parameter expression="${project.build.testSourceDirectory}"
	 * @required
	 * @readonly
	 */
	private File testSourceDirectory;

	/**
	 * @parameter expression="${project.build.resources}"
	 * @required
	 * @readonly
	 */
	private List<Resource> resources;

	/**
	 * @parameter expression="${project.build.testResources}"
	 * @required
	 * @readonly
	 */
	private List<Resource> testResources;

	/**
	 * The file types which will be included for counting
	 * 
	 * @parameter
	 */
	private String[] includes;

	public void execute() throws MojoExecutionException {

		if ( includes == null || includes.length == 0 ) {
			includes = INCLUDES_DEFAULT;
		}

		try {
			countDir( sourceDirectory );
			countDir( testSourceDirectory );
			for ( Resource resource : resources ) {
				countDir( new File( resource.getDirectory() ) );
			}
			for ( Resource resource : testResources ) {
				countDir( new File( resource.getDirectory() ) );
			}
		} catch ( IOException e ) {
			throw new MojoExecutionException("Unable to count lines of code.", e);
		}
	}

	private void countDir( File dir ) throws IOException { /* ... */ }

}

注释有@parameter的表示是可以配置的参数,比如includes就是可以配置的:

<plugin>
	<groupId>com.juvenxu.mvnbook</groupId>
	<artifactId>maven-loc-plugin</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<configuration>
		<includes>
			<include>java</include>
			<include>sql</include>
		</includes>
	</configuration>
</plugin>

这样的插件就可以在maven中使用了,可以加在pom文件里,也可以从命令行调用:

mvn com.juvenxu.mvnbook:maven-loc-plugin:0.0.1-SNAPSHOT:count

放在setting.xml里:

<settings>
	<pluginGroups>
		<pluginGroup>com.juvenxu.mvnbook</pluginGroup>
	</pluginGroups>
</settings>

以后就可以简化:

vmn loc:count

Mojo标注

  • @goal <name>:目标名称,命令行调用与pom中都要用。
  • @phase <phase>:默认绑定到default阶段
  • * @requiresDependencyResolution <compile/test/runtime>:默认要解析完runtime的依赖。
  • @requiresProject <true/false>:默认要在一个项目里才能执行
  • @requiresDirectInvocation <true/false>:默认不是只能在命令行调用执行
  • @requiresOnline <true/false>:默认不用在线状态
  • @requiresReport <true/false>:默认不要求报告已经生成
  • @aggregater:在有多个子模块时默认只在顶层模块运行,如生成javadoc
  • @execute goal="<goal>":在运行前选运行另一个目标。如果是本插件的目标直接用 目标名,其他插件的格式为prefix:goal
  • @execute phase="<phase>":先要运行某阶段
  • @execute lifecycle="<lifecycle>" phase="<phase>"

自定义生命周期配置文件位于:src/main/resources/META-INF/maven/lifecycle.xml

<lifecycles>
	<lifecycles>
		<id>surefire</id>
		<phases>
			<phase>
				<id>test</id>
				<configuration>
					<testFailureIgnore>true</testFailureIgnore>
				</configuration>
			</phase>
		</phases>
	</lifecycles>
</lifecycles>

Mojo参数

参数类型

  • boolean:Boolean boolean
  • int: Integer int Long long Short short Byte byte
  • float:Float float Double double
  • String:StringBuffer Character char
  • Data:yyyy-MM-dd HH:mm:ss.s ayyyy-MM-dd HH:mm:ssa。 例:<sampleDate>2010-06-06 3:14:55.1 PM</sampleDate> 或:<sampleDate>2010-06-06 3:14:55PM</sampleDate>
  • File:private File sampleFile<sampleFile>/tmp/aa.txt</sampleFile>
  • URL:private URL myURL<myURL>http://www.aa.com</myURL>
  • 数组:<includes><include>111</include><include>222</include></includes>
  • Collection的实现。
  • Map:<sampleMap><key1>aaa</key1><key2>bbb</key2></sampleMap>
  • Properties:<sampleProperties><preperty><name>attr1</name><value>val1</value></preperty><preperty><name>attr2</name><value>val2</value></preperty></sampleProperties>

parameter标签

@readonly只读

@required必填

@parameter alias="<aliasName>",给长参数起别名:

/**
	* @parameter alias="uid"
	*/
private String uniqueIdentity 
<uid>juven</uid>

@parameter alias="${aSystemProperty}":取系统参数

/**
	* @parameter expression="${maven.test.skip}"
	*/
private boolean skip;

@parameter default-value="aValue/${anExpression}":带默认值的

/**
	* @parameter defaultValue="true"
	*/
private boolean skip;

或对于必填项,就是默认值:

/**
	* @parameter expression="${maven.test.skip}"
	* @required
	*/
private boolean skip;

错误处理和日志

  • MojoFailureException,可预期的错误,如编译时失败
  • MojoExecutionException,不可预期的错误,如IO错误

错误日志,通过getLog()方法:

  • debug:默认不显示,-X参数打开
  • info
  • wran
  • error

测试Maven插件

使用脚本语言如BeanShell或Groovy比一般的测试方式更加方便。比如用 maven-invoker-plugin来调用groovy脚本:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-invoker-plugin</artifactId>
	<version>1.5</version>
	<configuration>
		<projectsDirectory>src/it</projectsDirectory>
		<goals>
			<goal>install</goal>
		</goals>
		<postBuildHookScript>validate.groovy</postBuildHookScript>
	</configuration>
	<executions>
		<execution>
			<id>integration-test</id>
			<goals>
				<goal>install</goal>
				<goal>run</goal>
			</goals>
		</execution>
	</executions>
</plugin>

再src目录下面再建立一个新的测试项目POM:

<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/maven-v4_0_0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<groupId>com.juvenxu</groupId>
	<artifactId>app</artifactId>
	<packaging>jar</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>app</name>
	<url>http://maven.apache.org</url>

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

	<build>
		<plugins>
			<plugin>
				<groupId>com.juvenxu.mvnbook</groupId>
				<artifactId>maven-loc-plugin</artifactId>
				<version>0.0.1-SNAPSHOT</version>
				<executions>
					<execution>
						<goals>
							<goal>count</goal>
						</goals>
						<phase>verify</phase>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-resources-plugin</artifactId>
				<configuration>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>			
		</plugins>
	</build>

</project>

验证的groovy脚本,直接读日志文件,检查是否有匹配的行:

def file = new File(basedir, 'build.log')

def countMain = false
def countTest = false

file.eachLine {
  if ( it =~ /src.main.java: 13 lines of code in 1 files/ )
    countMain = true
  if ( it =~ /src.test.java: 38 lines of code in 1 files/ )
    countTest = true
}

if ( !countMain )
  throw new RuntimeException( "incorrect src/main/java count info" ); 
  
if ( !countTest )
  throw new RuntimeException( "incorrect src/test/java count info" ); 

maven-invoker-plugin还可以有其他的配置点,比如

  • debug(boolean):是否打开debug日志。
  • settingsFile(File):用哪个settings.xml默认是本机的settings.xml
  • localRepositoryPath(File):用哪个本地仓库。
  • preBuildHookScript(String):构建前运行的BeanShell或Groovy脚本
  • postBuildHookScript