status: done in Maven Plugin Tools 3.0

Context

Maven plugins are described in META-INF/maven/plugin.xml descriptor, which is in general generated by maven-plugin-plugin. Information for the generator are written as javadoc annotations in java source: see maven-plugin-tools-java.

Using java 5 annotations instead of javadoc ones have multiple benefits:

  • compile-time checks for plugin metadata, with enums for some annotations
  • inheritance support
  • annotations are supported in most IDEs, providing code-completion and syntactic checks

see plexus-component-annotations for an example of such a work done on Plexus.

Existing implementations

Multiple implementations of such annotations exist:

Proposal

Continue the work started on MNG-2521 before plugin-tools extraction from Maven Core.

Annotations:

  • 4 annotations: @Mojo and @Execute at class-level, @Parameter and @Component at field level
  • java package: org.apache.maven.tools.plugin.annotations (consistent with maven-plugin-tools-api
  • a new maven-plugin-tools-annotations component in plugin-tools

Extractor: addition into existing maven-plugin-tools-java

New features

  • use annotations from parents classes coming from reactors and external dependencies.

Implementation

Initial work done in trunk http://svn.apache.org/repos/asf/maven/plugin-tools/trunk/

The maven-plugin-plugin version has been bump to 3.0-SNAPSHOT.

State of the development as of 22/5/2012

Following usage for a plugin developer is working, with annotations near previous javadoc tags:

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.InstanciationStrategy;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

/**
 * Mojo Description. @Mojo( name = "<goal-name>" ) is the minimal required annotation.
 * @since <since-text>
 * @deprecated <deprecated-text>
 */
@Mojo( name = "<goal-name>",
       aggregator = <false|true>,
       configurator = "<role-hint>",
       executionStrategy = "<once-per-session|always>",
       inheritByDefault = <true|false>,
       instantiationStrategy = InstanciationStrategy.<strategy>,
       defaultPhase = "<phase-name>",
       requiresDependencyResolution = ResolutionScope.<scope>,
       requiresDependencyCollection = ResolutionScope.<scope>, // (since Maven 3.0)
       requiresDirectInvocation = <false|true>,
       requiresOnline = <false|true>,
       requiresProject = <true|false>,
       requiresReports = <false|true>, // (unsupported since Maven 3.0)
       threadSafe = <false|true> ) // (since Maven 3.0)
@Execute( goal = "<goal-name>",
          phase = LifecyclePhase.<phase>
          lifecycle = "<lifecycle-id>" )
public class MyMojo
    extends AbstractMojo
{
    /**
     * @since <since-text>
     * @deprecated <deprecated-text>
     */
    @Parameter( alias = "myAlias",
                property = "a.property",
                defaultValue = "an expression with ${variables} eventually",
                readonly = <false|true>
                required = <false|true> )
    private String parameter;

    /**
     * @since <since-text>
     * @deprecated <deprecated-text>
     */
    @Component( role = MyComponentExtension.class,
                hint = "..." )
    private MyComponent component;

    public void execute()
    {
        ...
    }
}

Improvement Idea #1

rename roleHint to hint to match Plexus @Requirement

    @Component( role = MyComponentExtension.class,
                hint = "..." )
    private MyComponent component;

DONE

Improvement Idea #2

Remove readonly attribute from @Parameter (was used for Maven components like ${project} to mark that it should not be configured by plugin user), and replace by a new attribute of @Component which is mutually exclusive with role+hint

    @Component( "project" )
    private MavenProject project;

    @Component( role = MyComponentExtension.class,
                roleHint = "..." )
    private MyComponent component;

evaluation: ${project.compileClasspathElements} is an expression that would be readonly but doesn't really resolve to a component, so the term isn't appropriate. Need a better term.

Improvement Idea #3

Introduce JSR-330, by requiring plugin developer to mark injected fields with standard @Inject and make @Parameter and @Component standard Qualifier.
Costs more writing for plugin developer (these @Inject) without much win other than mark the intent in a standard way (and shouldn't change much for maven-plugin-tools)

    /**
     * @since <since-text>
     * @deprecated <deprecated-text>
     */
    @Inject
    @Parameter( alias = "myAlias",
                property = "aProperty",
                defaultValue = "${anExpression}",
                readonly = <false|true>
                required = <false|true> )
    private String parameter;

    /**
     * @since <since-text>
     * @deprecated <deprecated-text>
     */
    @Inject
    @Component( role = MyComponentExtension.class,
                roleHint = "..." )
    private MyComponent component;

evaluation: since plugin.xml is still generated and content is injected by Maven core using this plugin.xml and not annotations taken from bytecode, this is not really useful and causes more confusion/harm than benefit

  • No labels

5 Comments

  1. I think it is a bad smell to have annotations like @Mojo, @MojoExecute, @MojoParameter and @MojoComponent

    We should take use of the whole package namespacing thingy... no need to prefix with Mojo IMHO

    @Goal
    @Execute
    @Parameter
    @Component

    are better, e.g.

    @Goal(name="foo")
    public class FooMojo extends AbstractMojo {
      @Parameter
      private MavenProject project
    
    }
    

    is better than

    @Mojo(name="foo")
    public class FooMojo extends AbstractMojo {
      @MojoParameter
      private MavenProject project
    
    }
    

    Now I would see @Mojo being appropriate if we were ditching inheritance for mojo's, so that if you had

    @Mojo(name="foo")
    public class FooMojo {
      @Parameter
      private MavenProject project
    
      @Goal
      public void doExecuteGoal() {
        // equiv of AbstractMojo.execute without requiring the interface
      }
    }
    

    But actually, I would prefer in that case

    @Mojo
    public class FooMojo {
      @Parameter
      private MavenProject project
    
      @Goal(name="foo") 
      public void doExecuteGoal() {
        // equiv of AbstractMojo.execute without requiring the interface  
      }
    
      @Goal(name="bar")
      public void doOtherExecuteGoal() {
        // allowing multiple goals from the same shared common class
      }
    }
    

    I think the above could be implemented somewhat using synthentic bridging classes generated during the annotation processing

    1. Actually thinking about my final example, there is no need for the @Mojo annotation on the class in that case at all!

      public class FooMojos {
        @Parameter
        private MavenProject project
      
        @Mojo 
        public void foo() {
          // goal defaults to method name if not specified  
        }
      
        @Mojo(goal="foo-bar")
        public void fooBar() {
          // allowing multiple goals from the same shared common class
        }
      }
      
      1. ok, no "Mojo" prefix for Execute, Parameter and Component

        for the multiple goals in a single class, yes, a future feature can be added when @Mojo is used as a method annotation
        I only see one issue to implement this feature: annotations are available after compilation, but such a feature would require generating source, then before compilation...
        I don't think that this feature qill be available for the first release, but we can work on it after if really requested

        1. Well I was thinking that using ASM you could just generate the synthetic bridging classes in the process-classes phase. The bytecode would be simple as it will always do the exact same

          1. Ahh yes... I'm seeing the chicken and egg issue....

            Of course the chicken and egg issue also exists for the help mojo that gets generated...