Search code examples
javamavenmaven-pluginexec-maven-plugin

How to use Maven Exec Plugin as Library in another plugin?


I currently am using the Exec Maven Plugin and it works fine with:

<plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>exec-maven-plugin</artifactId>
      <version>1.4.0</version>
      <executions>
        <execution>
          <id>myExec</id>
          <goals>
            <goal>exec</goal>
          </goals>
          <phase>generate-sources</phase>
          <configuration>
            <executable>myExec</executable>
            <arguments>
              <argument>--foo=${basedir}/src/test/resources/test.xml</argument>
              <argument>--output-directory=target/generated-sources/output</argument>
            </arguments>
          </configuration>
        </execution>
      </executions>
    </plugin> 

I am also using the build helper plugin as follows:

<plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>build-helper-maven-plugin</artifactId>
        <version>1.10</version>
        <executions>
          <execution>
            <phase>generate-sources</phase>
            <goals>
              <goal>add-source</goal>
            </goals>
            <configuration>
              <sources>
                <source>${project.build.directory}/generated-sources/output</source>
              </sources>
            </configuration>
          </execution>
        </executions>
      </plugin>

This is incredibly verbose however and I want multiple maven modules to be able to use this program and not have to retype all the exec plugin specific XML along with the builder XML.

Question: How do I possibly combine these 2 into another plugin?

I have used the maven archetype generator to generate a sample maven plugin and have a Mojo class:

@Mojo(name = "touch", defaultPhase = LifecyclePhase.PROCESS_SOURCES)
public class MyMojo
        extends AbstractMojo
{


    public void execute()
            throws MojoExecutionException
    {
        ExecMojo exec = new ExecMojo();
    }
}

And have figured out how to create a new ExecMojo.

Question How do I add the arguments here as I would in the XML above? And how can I integrate these arguments into my plugin?


Solution

  • Instead of creating your own Maven plugin, which may reduce portability and maintenability of your project, you may consider the following approach instead:

    • Have a common parent pom
    • Configure the given plugins configuration, optionally in a Maven profile
    • In the concerned module, point to this parent. Optionally (in case of profiled configuration) activate it on demand when required.

    A simple parent pom would look like the following:

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.sample</groupId>
        <artifactId>sample-maven-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>pom</packaging>
    
        <properties>
            <path.to.myexec>path</path.to.myexec>
        </properties>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>1.4.0</version>
                    <executions>
                        <execution>
                            <id>myExec</id>
                            <goals>
                                <goal>exec</goal>
                            </goals>
                            <phase>generate-sources</phase>
                            <configuration>
                                <executable>${path.to.myexec}\myExec</executable>
                                <arguments>
                                    <argument>--foo=${basedir}/src/test/resources/test.xml</argument>
                                    <argument>--output-directory=${project.build.directory}/generated-sources/output</argument>
                                </arguments>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <version>1.10</version>
                    <executions>
                        <execution>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>add-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>${project.build.directory}/generated-sources/output</source>
                                </sources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
        <profiles>
            <profile>
                <id>myexec-profile</id>
                <build>
                    <plugins>
                        <!-- optionally move here the configuration above -->
                    </plugins>
                </build>
            </profile>
        </profiles>
    </project>
    

    Note the path.to.myexec property I added, to be overriden in children projects if required, in order to point to the correct relative path.

    Then, once installed in your machine (or deployed in your company Maven repository), it can be referenced as following in any concerned Maven projects:

    <parent>
        <groupId>com.sample</groupId>
        <artifactId>sample-maven-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    

    No need to re-declare the plugin configurations above and its verbose approach. They will be automatically part of the defaul build as part of the inherited build configuration from this parent.


    The profiled approach can also be a solution if:

    • You want to reuse an existing parent pom already used by projects which don't need this configuration
    • You don't always need this behavior on your build and you want to activate it on demand

    In such a case, you can then activate it via, as an example:

    mvn clean package -Pmyexec-profile
    

    Given that you already set the parent accordingly and you moved into the profile the configuration above.


    Advantages of this approach:

    • lighter option than writing a new maven plugin (which needs to be written, tested, maintained, distributed, etc.)
    • easier for consumer modules to customize something: at any moment they can override parent's configuration as an exception
    • less fragile: just imagine what if another version of one of these plugins provides a bug fix important for you, that's easy to configure an XML, much less easy to change the customized maven plugin etc.
    • configuration remains centralized, transparently accessible and entry point for further governance
    • easier troubleshooting: at any moment a consumer module can run mvn help: effective-pom and see the merged full effective pom (as aggregate of parent and current pom) and check what it's actually running

    How to skip parent plugin executions in certain modules

    A simple (and often used) approach to execute this plugins only in certain modules while having the parent in common with other modules is the following:

    • Define a new property, let's call it skip.script.generation, with default value to true, defined in the parent pom.
    • Use this skip property in the skip configuration entry of the plugins above.
    • Re-define the property only in the concerned modules and set it to false. This will be the only configuration required for their pom.xml files, hence reduced to one line (keeping verbosity really low).

    The exec-maven-plugin provides such a skip option, unfortunately the build-helper-maven-plugin doesn't. But that's not blocking us. We can still skip the two executions playing with their phase element, setting it to a non existing phase, like none and as such skipping them. This is suitable because the two executions are actually already attached to the same phase, generate-sources.

    For this approach, let's rename our new property to script.generation.phase.

    As an example:

    <project>
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.sample</groupId>
        <artifactId>sample-maven-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>pom</packaging>
    
        <properties>
            <path.to.myexec>path</path.to.myexec>
            <script.generation.phase>none</script.generation.phase>
        </properties>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>1.4.0</version>
                    <executions>
                        <execution>
                            <id>myExec</id>
                            <goals>
                                <goal>exec</goal>
                            </goals>
                            <phase>${script.generation.phase}</phase>
                            <configuration>
                                <executable>${path.to.myexec}\myExec</executable>
                                <arguments>
                                    <argument>--foo=${basedir}/src/test/resources/test.xml</argument>
                                    <argument>--output-directory=${project.build.directory}/generated-sources/output</argument>
                                </arguments>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>build-helper-maven-plugin</artifactId>
                    <version>1.10</version>
                    <executions>
                        <execution>
                            <phase>${script.generation.phase}</phase>
                            <goals>
                                <goal>add-source</goal>
                            </goals>
                            <configuration>
                                <sources>
                                    <source>${project.build.directory}/generated-sources/output</source>
                                </sources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
        <profiles>
            <profile>
                <id>myexec-profile</id>
                <build>
                    <plugins>
                        <!-- optionally move here the configuration above -->
                    </plugins>
                </build>
            </profile>
        </profiles>
    </project>
    

    Note the <phase>${script.generation.phase}</phase> changes for both plugins. With its default value to none, this property is effectively disabling their executions by default.

    In another module you would then have the following:

    <parent>
        <groupId>com.sample</groupId>
        <artifactId>sample-maven-parent</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    
    <properties>
        <script.generation.phase>generate-sources</script.generation.phase>
    </properties>
    

    And nothing else. That is. Maven during the build will re-define the property for a certain module and automatically replace it in the configuration inherited from its parent and as such enabling again the two executions above.