雨中的阳光Seattle

靠着很近的Redmond

Maven Build Lifecycle 2010年04月3日

Filed under: Uncategorized — systembug @ 8:35 上午

Maven 2是围绕着Build Lifecycle概念设计的。这意味着,构建或者发布的过程已经被清晰的定义了。
Build lifecycle是由a set有依次顺序的phases组成的。当我们使用Maven构建工程时,我们只需要了解自己希望做什么,然后执行对应的lifecycle phase即可。
例如,我们希望编译我们的工程,在命令行状态下进入到工程的pom.xml文件所在的目录中,使用命令:mvn compile;希望构建打包我们的工程,使用mvn package即可。
每个LifeCycle phase实质上都会绑定到某个plugin:goal上!!例如命令
mvn compile
实际上等价于
            mvn compiler:compile
即执行compile phase命令=执行compiler plugin的compile goal
但有一个很关键的知识点:pom.xml的<packaging>的值不同,会影响lifecycle phase与哪个plugin:goal的绑定。例如:
如果是
<packaging>jar</packaging>
那么
package phase绑定到jar:jar

            mvn package = mvn jar:jar
如果是
<packaging>war</packaging>
那么
package phase绑定到war:war

            mvn package = mvn war:war
在default lifecycle里,包含下列最常用被执行的phases(文章最后会给出lifecycle phases的完整列表):
validate : validate the project is correct and all necessary information is available
compile : compile all java files in project
test : 使用unit test framework来test the compiled source code。这些test class不会被packaged and deployed。
package : 把compiled code打包成你需要的格式,如JAR.
integration-test : 把已经打包的东东(如jar)deploy到运行环境内,用于集成测试
verify : run any checks to verify the package is valid and meets quality criteria
install : install the package into the local repository, 这样做的目的是作为其他projects的一个local dependency
deploy : 该phase会copy final package到remote repository for sharing。This phase is done in an integration or release environment。
另外还有3个很常用的、但不属于default lifecycle的phase:
clean : cleans up artifacts created by prior builds
site : generates site documentation for this project
eclipse:eclipse : 创建相关的eclipse ide文件  
当你执行一个phase命令时,maven就会执行顺序排在该phase之前的所有phases,然后再执行该phase。例如,如果我们执行“compile” phase,那么maven实际要执行的phases依次为:
1.       validate
2.       generate-sources
3.       process-sources
4.       generate-resources
5.       process-resources
6.       compile
一个mvn命令可以同时执行多个phases,如:
mvn clean install
该命令是先执行clean phase,然后执行install phase
<packaging>与lifecycle phase
pom.xml的<packaging>的值(缺省为)会直接影响mvn lifecycle phase的执行操作,即不同的值将直接影响lifecycle phase与哪个plugin:goal的绑定。
例:
1)如果是<packaging>jar</packaging>,那么lifecycle phases与plugin:goal的绑定为(左边为lifecycle phase,右边为实际操作plugin:goal):
process-resources resources:resources
compile compiler:compile
process-test-resources resources:testResources
Test-compile compiler:testCompile
Test surefire:test
package jar:jar
install install:install
deploy deploy:deploy

看上面列表,其中最值得注意的是package phase绑定到了jar:jar上,
执行mvn package = mvn jar:jar
2)如果是<packaging>war</packaging>,那么package phase会绑定到war:war上,执行mvn package = mvn war:war
3)如果是<packaging>pom</packaging>,那么上面列表中只有“install” and “deploy”被绑定到,而其他phases则没有被绑定到。也就是说如果执行mvn package,则什么操作也没执行。
另外一个很重要的知识点是:有一些<packaging>值的使用,必须在pom.xml里添加相应的<plugin>配置信息。
例如:如果你要使用
<packaging>plexus-application<packaging>
or
<packaging>plexus-service<packaging>
就必须在pom.xml里添加“Plexus” plugin配置。
<plugin> 与lifecycle phase
<plugin> element可以给lifecycle phase添加要执行的goal,<plugin>设置的goal将会被添加到goal list goals which already bound to the lifecycle from the packaging selected。这时某个phase被绑定了1个以上的goal,那么在执行该phase的mvn命令时,会先执行<packaging>本身所绑定的goal,然后再执行<plugin>绑定的goals。你更可以使用<plugin>的child element <executions>来控制goal的执行顺序。
例:有一个Modello plugin,它总是绑定modello:java到generate-sources phase(注意:设置绑定不是在你的pom.xml里,而是plugin本身已经设置好了的!!)
如果你的project想使用Modello plugin来generate sources from a model,那么就需要在pom.xml里的<build>里的<plugins>里添加下列代码:

<plugin>
   <groupId>org.codehaus.modello</groupId>
   <artifactId>modello-maven-plugin</artifactId>
   <executions>
     <execution>
       <configuration>
         <model>maven.mdo</model>
         <modelVersion>4.0.0</modelVersion>
       </configuration>
       <goals>
         <goal>java</goal>
       </goals>
     </execution>
   </executions>
</plugin>

为什么要使用<executions>?这是因为使用它可以对同一个goal根据不同的<configuration>来执行多次。
如果有多个匹配某个phase的<execution>,那么他们将会被依次按顺序执行(当然,被继承的execution会先被执行)
我们再来考虑这样一种情况:上面提到Modello plugin的goal(modello:java)被绑定到generate-sources phase(注意:设置绑定不是在你的pom.xml里,而是plugin本身已经设置好了的!!),如果我还希望该goal能够同时也绑定到另外的、非内在绑定的phase上,怎么办呢?很简单,只需要在<execution>里使用<phase>来设置。例如,你有一个goal touch:timestamp,它是用来输出某个file的timestamp,该goal内在绑定到test phase,你希望能把它也绑定到process-test-resources上,则使用下列代码:

<plugin>
   <groupId>com.mycompany.example</groupId>
   <artifactId>touch-maven-plugin</artifactId>
   <executions>
     <execution>
       <phase>process-test-resources</phase>
       <configuration>
         <file>${project.output.directory}/timestamp.txt</file>
       </configuration>
       <goals>
         <goal>timestamp</goal>
       </goals>
     </execution>
   </executions>
</plugin>

lifecycle phases的完整列表(按执行的先后顺序)
validate validate the project is correct and all necessary information is available.
generate-sources generate any source code for inclusion in compilation.
process-sources process the source code, for example to filter any values.
generate-resources generate resources for inclusion in the package.
process-resources copy and process the resources into the destination directory, ready for packaging.
compile compile the source code of the project.
process-classes post-process the generated files from compilation, for example to do bytecode enhancement on Java classes.
generate-test-sources generate any test source code for inclusion in compilation.
process-test-sources process the test source code, for example to filter any values.
generate-test-resources create resources for testing.
process-test-resources copy and process the resources into the test destination directory.
test-compile compile the test source code into the test destination directory
test run tests using a suitable unit testing framework. These tests should not require the code be packaged or deployed.
prepare-package perform any operations necessary to prepare a package before the actual packaging. This often results in an unpacked, processed version of the package. (Maven 2.1 and above)
package take the compiled code and package it in its distributable format, such as a JAR.
pre-integration-test perform actions required before integration tests are executed. This may involve things such as setting up the required environment.
integration-test process and deploy the package if necessary into an environment where integration tests can be run.
post-integration-test perform actions required after integration tests have been executed. This may including cleaning up the environment.
verify run any checks to verify the package is valid and meets quality criteria.
install install the package into the local repository, for use as a dependency in other projects locally.
deploy done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects.

Build lifecycle与plugin开发
开发Plugin简单来说就是开发一组“根据输入和输出参数来执行某些操作”的goals(也就是mojo),然后把goal绑定到相应的lifecycle phase上去。
Plugin通过3种方式来与lifecycle phase进行绑定:
绑定一个mojo到一个特定的phase上
定义一个packaging type(即定义一个pom.xml中<packaging>的值),并设置相应的lifecycle bindings
forking a parallel lifecycle
Binding a Mojo to a Phase
在mojo上添加annotation @phase来把mojo绑定到某个lifecycle phase上。如下面的annotation就是把mojo绑定到generate-sources phase上:
@phase generate-sources
这样,当MOJO对应的GOAL在POM.XML里有进行设置,那么该goal就会在绑定的phase里被执行。
定义一个packaging type(即定义一个pom.xml中<packaging>的值)
如果你的plugin希望提供一个特别的artifact type,也就是提供一个特别的<packaging>值,那么你不仅需要提供package phase要绑定的goal,还需要提供其他default lifecycle phases与goals的绑定。
定义packaging type是在plugin jar的META-INF/plexus/components.xml里。下面的例子是Plexus plugin在register一个packaging type:  plexus-application。
<component-set>
<components>
    <component>
      <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
      <role-hint>plexus-application</role-hint>
      <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
      <configuration>
        <phases>
          <process-resources>org.apache.maven.plugins:maven-resources-plugin:resources</process-resources>
          <compile>org.apache.maven.plugins:maven-compiler-plugin:compile</compile>
          <process-test-resources>org.apache.maven.plugins:maven-resources-plugin:testResources</process-test-resources>
          <test-compile>org.apache.maven.plugins:maven-compiler-plugin:testCompile</test-compile>
          <test>org.apache.maven.plugins:maven-surefire-plugin:test</test>
          <package>org.codehaus.plexus:plexus-maven-plugin:app</package>
          <install>org.apache.maven.plugins:maven-install-plugin:install</install>
          <deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
        </phases>
      </configuration>
    </component>
</components>
</component-set>
在上面的代码中,
<role-hint>的值是新定义的packaging type。
<role>的值为org.apache.maven.lifecycle.mapping.LifecycleMapping,表示这是为packaging进行的lifecycle绑定。
<implementation> element是必须的,该例子是使用缺省的implementation.
<configuration> element里是为这个新定义的packaging type设置被绑定的phases list。列表中的每一个phase都会被绑定到一个goal上。请注意goal设置必须是"fully qualified"的(格式为:groupId:artifactId:goal or groupId:artifactId:version:goal)。
值得留意的是:上述代码只是把package phase绑定到自定义plugin的app goal上,其他phase还是绑定到缺省的goal上。
针对上面代码定义的packaging type “plexus-application”,如果你要在你的project里使用它,你的pom.xml应该类似于:

<packaging>plexus-application</packaging>

<plugin>
   <groupId>org.codehaus.plexus</groupId>
   <artifactId>plexus-maven-plugin</artifactId>
   <extensions>true</extensions>
</plugin>

注意:如果你使用自定义的artifact type handlers,那么就必须设置<extensions>。
One final task that is required is for the packaging goal you have created to tell Maven where to find what you built. This is done with code such as the following:
project.getArtifact().setFile( new File( "target/myFile-2.0.jar" ) );
如何定义一个Custom Artifact Handler
缺省的artifact handler会packaging, type, and file extension映射成相同的值。例如,如果你定义了一个packaging为“plexus-application”,那么在repository里对应的file为“myartifactId-1.0.plexus-application”。这个规则也适用于a particular type from a dependency.
如果你想修改extension, 或者想修改a dependency’s type field映射到其他fields的方式,你必须在你的plugin jar里的META-INF/plexus/components.xml里配置一个custom artifact handler。
下面代码是A complete artifact handler for the test-jar type:
<component-set>
<components>
    <component>
      <role>org.apache.maven.artifact.handler.ArtifactHandler</role>
      <role-hint>test-jar</role-hint>
      <implementation>org.apache.maven.artifact.handler.DefaultArtifactHandler</implementation>
      <configuration>
        <classifier>tests</classifier>
        <extension>jar</extension>
        <type>test-jar</type>
        <packaging>jar</packaging>
        <language>java</language>
        <addedToClasspath>true</addedToClasspath>
      </configuration>
    </component>
</components>
</component-set>
The fields are configured as follows:
Field Required? Default Description
role-hint Y   The type being defined.
type Y   Must match the role-hint .
extension N The type The extension to give the artifact in the repository.
packaging N The type The packaging of the artifact to look for.
classifier N   The classifier to append to the artifact name (after version and before extension) when using this type.
language N java The language the artifact is written in. No set values – free text.
addedToClasspath N true Whether the artifact should be included in a classpath or library path when used.
includesDependencies N false If the artifact already includes all of its dependencies, this setting ensures they are not propogated transitively.

因此,只要该plugin被declare同时type被用到,如果使用custom artifact handler,就必须设置<extensions> element。
Forking a Parallel Lifecycle
While lots of mojos will participate in the standard lifecycle, there are just as many that are used in other scenarios. These are mojos that are executed standalone from the command line (such as idea:idea ), or individual reports in the site building process.
However, sometimes these goals require that a particular task has already been performed – for instance, the IDEA plugin must ensure sources have been generated to properly construct its module files. If the goal were participating in the lifecycle, it would easily do this by ensuring it occurred after the phase it depended on having run. Since this isn’t the case, it must have a way to first execute that task.
Additionally, even goals participating in the build lifecycle might need to perform a task with different parameters to what was already used, and does not want the output to affect the current build (for example, running clover:check to run tests with modified sources and fail if a certain coverage ratio is not achieved).
For these reasons, mojos are capable of forking a new lifecycle. The lifecycle will be a normal build lifecycle, a clone of the one currently being used (including any additional bindings from the POM), executed up until the point specified by the mojo.
For example, the idea:idea mojo specifies the following in the mojo level declarations to call the source generation:
@execute phase="generate-sources"
But what happens if generate-sources has already been run in this build? In the current version of Maven, there is no way to tell if the previous execution used the same input and outputs as the current mojo requires, so the task (and any preceding ones if from the lifecycle) must be run again.
For this reason, it is important that if your plugin does any intensive work, you should first check whether it is necessary to perform the tasks again, perhaps by using timestamp checking or a similar technique. As an example, the compiler plugin will only recompile changed source files so can very efficiently be run multiple times in a build if necessary.
When the lifecycle is forked, the project object being used is also cloned. In this way, modifications made to the project as part of the execution, such as the addition of a new source root, will not affect the original build. When the lifecycle finishes executing and control is passed to the original mojo, it can access that project using the expression $executedProject . For example:
/**
* @parameter expression="${executedProject}"
*/
private MavenProject executedProject;
This project instance can be used by the mojo to obtain results, and propogate any changes it sees fit into the original build.
Finally, when forking the new lifecycle, it is possible to augment it on top of the changes already made by the packaging and the plugins in the POM.
For example, consider the Clover plugin. If clover:check were to be run from the command line, the plugin would need to fork the lifecycle, executing the test phase. But, it would also need to add some configuration and bind the clover:compiler goal to the generate-sources phase.
This can be achieved by including the following file as META-INF/maven/lifecycle.xml in the plugin JAR:
<lifecycles>
<lifecycle>
    <id>clover</id>
    <phases>
      <phase>
        <id>generate-sources</id>
        <executions>
          <execution>
            <configuration>
              <debug>true</debug>
            </configuration>
              <goals>
                <goal>compiler</goal>
              </goals>
          </execution>
        </executions>
      </phase>
    </phases>
</lifecycle>
</lifecycles>
Here, the executions element is present in a similar way to a plugin declaration in the POM. This can be used to bind a goal one or more times to a particular phase, as well as specifying configuration. Note that configuration already provided in the POM to that plugin that is not part of a specific execution will also be applied.
The lifecycle ID given here (clover ) can then be used in the mojo to specify what to overlay on the forked lifecycle when executing it, using the following mojo level declaration:
@execute phase="test" lifecycle="clover"

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/totogogo/archive/2007/12/17/1942867.aspx

Advertisements
 

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s