Saltar a contenido

MAVEN PLUGIN

1.Introduccion

Maven es una herramienta de gestion de todo el ciclo de vida de desarrollo de un software.

El ciclo de vida basico de un software se consituye a grandes rasgos de unas fases basicas:

  1. Creacion: Da soporte mediantes Arquetipos (son plantillas para generar artefactos de software predefinidos, dandonos agilidad, rapidez y uniformidad en nuestros desarrollos)

  2. Compilacion: Traspaso al lenguaje maquina..

  3. Instalacion: El codigo generado se empaqueta para ser distribuido

  4. Despliegue: El codigo se distribuye y se ejecuta

Para que se lleven a cabo estos pasos fundamentales maven define varias fases y en cada una de ellas realiza tareas que ayudan a todo el proceso.

Como vemos, maven pasa por multitud de tareas a lo largo del ciclo de vida Offical Reference.

Maven Phases

Asi vemos que es una cadena de fases:

Cada vez que ejecutamos una tarea en maven (por ejemplo : “mvn test”), maven recorrerá todas las phases anteriores hasta llegar a la phase indicada. .

2.MAVEN PLUGIN

En cada phase del ciclo de vida se ejecuta codigo que realiza una determinada tarea. Estas encapusulaciones de ejecucion asociadas a una determinada fase maven las agrupa en los llamados maven-plugin.

Un maven plugin no es mas que un fragmento de codigo que se ejecuta en una determinada phase del ciclo de vida.

Seguro que te suenan maven-resources-plugin, maven-compiler-plugin, maven-assembly-plugin, maven-deploy-plugin, etc..

3.AGENTES MAVEN

Como hemos comentado Maven nos ayuda en el proceso de creacion de software. La fase de empaquetado del software es un proceso crucial, ya que nos permite agrupar codigo de una manera uniforme.

Cuando construimos software con Maven necesitamos unicamente un fichero xml llamado POM.XML (Project Object Model).

Ese fichero contiene toda la meta informacion que necesita la herramienta maven para llevar a cabo todas sus tareas relacionadas con el ciclo de vida.

Dentro de este fichero encontramos la informacion relacionada con cual será el modo de empaquetado de nuestro software. Se encuentra en la etiqueta <packaging>

Conocemos el valor para crear un software de tipo BOM, POM, JAR, WAR, EAR, etc…

Para crear y empaquetar un software de tipo maven plugin usaremos

<packaging>maven-plugin</packaging>

4.CREANDO UN MAVEN PLUGIN

Como hemos dicho Maven nos ayuda en el proceso de creacion de software, asi que nos ofrece plantillas (maven archetypes) para que podamos iniciar el desarrollo de nuestro software de una manera agil, rapida y uniformide en nuestros desarrollos.

5.MAVEN-ARCHETYPE-MOJO

Maven nos ofrece de serie un arquetipo para empezar a desarrollar un software de tipo maven-plugin.

mvn archetype:generate  \
 -DgroupId=com.example  \
 -DartifactId=amazing-maven-plugin \ 
 -Dversion=0.0.1-SNAPSHOT  \ 
 -DarchetypeGroupId=org.apache.maven.archetypes   \
 -DarchetypeArtifactId=maven-archetype-mojo

Es importante mencionar que el nombre de tu plugin debe seguir la nomenclatura “\${pluginName}-maven-plugin” ya que los plugins internos del core de maven tienen nomenclatura “maven-${pluginName}-plugin” (maven-compiler-plugin, maven-resources-plugin, etc..).

La Referencia oficial nos dice esto:

maven-${prefix}-plugin - for official plugins maintained by the Apache Maven team itself (you must not use this naming pattern for your plugin, see this note for more informations)
${prefix}-maven-plugin - for plugins from other sources

Despues de la primera ejecucion del arquetipo obtenemos como resultado :

Un descriptor de software Maven (POM) que indica que se empaquetara como Maven-plugin y nos importa maven-plugin-api (que contiene las clases Core del maven-plugin-api)

Maven Archetype Result

Una clase Java que extiende de AbstractMojo (maven-plugin-api dependency) con unas anotaciones en su javadoc y un codigo a ejecutar en su metodo execute();

Maven Archetype Result

El codigo fuente generado es un plugin que se ejecutara en la @phase process-sources y que el nombre de esta tarea (goal) es touch , que este plugin para su ejecucion requiere un parametro (outputDirectory) que se resolvera de la expression “${project.build.directory}” (directorio del proyecto sobre el que este actuando la ejecucion de este plugin).

Si nos fijamos en la implementacion del metodo execute, crea un fichero en el directorio en el que se encuentre.

Asi vemos que el maven-archetype-mojo nos crea un plugin que su tarea llamada touch (goal) se ejecutara en la phase process-sources y que creara un fichero en el directorio raiz del proyecto ddonde se ejecuta.

6.MAVEN MOJO

Como vemos, una tareaque se ejecuta dentro de un proceso maven es un MOJO (Maven plain Old Java Object), es lo que conocemos comunmente como un goal.

Un maven-plugin, en este caso amazing-maven-plugin puede contener varios MOJO (goals / tareas), cada MOJO estara definido en su propia clase java.

Es decir podriamos crear otras clases java en este proyecto, darles un nombre goal y asociarlas a una phase maven y todo se empaquetaria en el mismo maven-plugin.

7.FANTASMAS DEL PASADO

Si ejecutamos el proceso de construccion de nuestro maven plugin veremos que falla:

Maven Archetype Result

Tristemente la ultima release del maven-archetype-mojo es de 2006.

Maven Archetype Result

7.1 MODIFICACIONES

Vamos a pasarlo a JAVA 17, añadimos en el pom.xml:

    <properties>
        <maven.compiler.target>17</maven.compiler.target>
        <maven.compiler.source>17</maven.compiler.source>
    </properties>

Supongo que a ti tambien se te ha hecho raro que la definicion de la phase y el nombre del goal estuviera dentro de un javadoc… En 2006 seguro que tenia sentido, pero vamos a darle un lavado de cara mas actual y vamos a utilizar la anotacion @Mojo Importamos la libreria para hacer uso de las anotaciones:

        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.6.0</version>
            <scope>provided</scope>
        </dependency>
Sustituimos la informacion del javadoc para hacer uso de la anotacion @Mojo y vamos a quitar el parameter y simplificamos la ejecucion del metodo execute con el fin de que esta demo sea mas clara.
...
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;

/**
 * Goal which touches a timestamp file.
 */
@Mojo(name = "touch", defaultPhase = LifecyclePhase.PROCESS_CLASSES)
public class MyMojo extends AbstractMojo{ 

    public void execute() throws MojoExecutionException
    {
        this.getLog().info(" ----- I AM THE TOUCH EXECUTION -----");
    }
}
...
MAVEN-PLUGIN-PLUGIN : Como comenté anteriormente, maven usa sus MOJO a lo largo del ciclo de vida del software que estamos creando. En este caso estamos creando un maven-plugin, maven para procesar este tipo de artefacto usa un mojo que se encuentra dentro del maven-plugin-plugin.

Recuerda en maven todo son MOJOs que se almacenan en plugins

Con las modificaciones anteriores si ejecutamos mvn clean install vemos que se esta ejecutando el maven-plugin-plugin:

Maven Archetype Result

Vemos que esta ejecutando la version 3.2 del maven-plugin-plugin en alguna fase posterior al compile.

Como hemos cambiado el procesado de la metainformacion (goal, phase, etc..) y estamos haciendo uso de anotaciones vamos a indicar especificamente en el proyecto que queremos hacer uso de una version mas actual del maven-plugin-plugin (3.8.1) que esta si que soporta las annotations.

Añadimos al pom.xml

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.8.1</version>
                <executions>
                    <execution>
                        <id>mojo-descriptor</id>
                        <goals>
                            <goal>descriptor</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Ahora si que podemos compilar y empaquetar correctamente:

Maven Archetype Result

8. EMPAQUETADO ¿QUE HEMOS OBTENIDO?

Del procesado del maven-plugin-plugin obtenemos un plugin en formato JAR que contiene el compilado del codigo a ejecutar y la metainformacion que describe el plugin.

Vamos a analizar el resultado.

Explorando el jar generado en /target/amazing-maven-plugin-0.0.1-SNAPSHOT.jar encontramos los .class, pero encontramos un fichero con metainformacion en /META-INF/maven/plugin.xml:

<?xml version="1.0"?>
<!--  Generated by maven-plugin-tools 3.8 -->
<plugin>
  <name>amazing-maven-plugin Maven Mojo</name>
  <description/>
  <groupId>com.example</groupId>
  <artifactId>amazing-maven-plugin</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <goalPrefix>amazing</goalPrefix>
  <isolatedRealm>false</isolatedRealm>
  <inheritedByDefault>true</inheritedByDefault>
  <requiredJavaVersion>17</requiredJavaVersion>
  <requiredMavenVersion>2.0</requiredMavenVersion>
  <mojos>
    <mojo>
      <goal>touch</goal>
      <description>Goal which touches a timestamp file.</description>
      <requiresDirectInvocation>false</requiresDirectInvocation>
      <requiresProject>true</requiresProject>
      <requiresReports>false</requiresReports>
      <aggregator>false</aggregator>
      <requiresOnline>false</requiresOnline>
      <inheritedByDefault>true</inheritedByDefault>
      <phase>process-classes</phase>
      <implementation>com.example.MyMojo</implementation>
      <language>java</language>
      <instantiationStrategy>per-lookup</instantiationStrategy>
      <executionStrategy>once-per-session</executionStrategy>
      <threadSafe>false</threadSafe>
      <parameters/>
    </mojo>
  </mojos>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <type>jar</type>
      <version>2.0</version>
    </dependency>
  </dependencies>
</plugin>
Lo fundamental que vemos aqui es :

  1. goalPrefix: en la resolucion de goals Maven usa una nomenclatura. Ya veremos esta property mas adelante
  2. requiredJavaVersion: Version de java minima requerida (ya que se compilo con java 17)
  3. requiredMavenVersion: Minima version de maven para ejecutar el plugin

Y vemos que ha analizado nuestra clase anotada con @Mojo y ha creado una seccion para este fichero de meta informacion: 4. touch: nombre del goal 5. true: Indica si el plugin se puede ejecutar Standalone o si necesita un projecto donde ejecutarse. ahora mas adelante lo veremos. 6. process-classes: La phase de maven asociada para la ejecucion de este goal 7. com.example.MyMojo: Codigo fuente

9.EJECUTANDO UN PLUGIN

Tenemos 2 modos de ejecutar un goal de un plugin de Maven.

  1. Invocacion transitiva: Nuestro plugin existe definido dentro de un proyecto (POM) y cuando llega su phase se ejecuta. Es el modo mas común que habras utilizado.

  2. Invocacion directa: Es decir no ejecutamos el plugin dentro del ciclo de vida, si no que lo ejecutamos a mano. Un ejemplo claro es el mvn archetype:create (estamos ejecutando el goal create del maven-archetype-plugin).

Para la invocacion directa necesitamos indicar las coordenadas del artefacto maven y el goal a ejecutar.

Vamos a un directorio vacio y ejecutamos en este caso:

# Create empty dir
$ mkdir tmp
$ cd tmp
$ mvn com.example:amazing-maven-plugin:0.0.1-SNAPSHOT:touch

[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< com.example:amazing-maven-plugin >------------------
[INFO] Building amazing-maven-plugin Maven Mojo 0.0.1-SNAPSHOT
[INFO] ----------------------------[ maven-plugin ]----------------------------
[INFO] 
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default-cli) @ amazing-maven-plugin ---
[INFO]  ----- I AM THE TOUCH EXECUTION -----
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.086 s
[INFO] Finished at: 2023-03-23T08:17:02+01:00
[INFO] ------------------------------------------------------------------------
Vemos que no ha pasado por las fases habituales de clean, install, etc.. puesto que hemos ejecutado el goal en modo directo sin pasar por el ciclo de vida completo.

9.1 REQUIRES PROJECT

Esta propiedad de la anotacion @Mojo indica si el plugin necesita ejecutarse en un contexto donde exista un proyecto Maven(POM). Por ejemplo maven-compiler-plugin es un claro ejemplo. Necesita unos ficheros .java preexistentes para poder realizar su trabajo.

Vamos a cambiar la property de la anotacion:

@Mojo(name = "touch", requiresProject = true, defaultPhase = LifecyclePhase.PROCESS_CLASSES)
public class MyMojo extends AbstractMojo { 
Compilamos y empaquetamos el plugin (mvn clean install).

Analizamos el descriptor META-INF/maven/plugin.xml y vemos que el descriptor ha cambiado:

<requiresProject>true</requiresProject>

Volvemos a ejecutar el goal del plugin en modo directo:

$ mvn com.example:amazing-maven-plugin:0.0.1-SNAPSHOT:touch
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.053 s
[INFO] Finished at: 2023-03-23T08:27:19+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal com.example:amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default-cli): Goal requires a project to execute but there is no POM in this directory (/home/tutorials/tmp). Please verify you invoked Maven from the correct directory. -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MissingProjectException

Necesitamos un proyecto Maven existente que sirva de host para la ejecucion. Creamos un proyecto usando uno de los arquetipos que ofrece maven

$ mvn archetype:generate -B \
-DgroupId=com.examples \
-DartifactId=project \
-Dversion=1.0-SNAPSHOT \
-DarchetypeGroupId=org.apache.maven.archetypes \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4

...
...

[INFO] Project created from Archetype in dir: /home/tutorials/tmp/project
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  9.279 s
[INFO] Finished at: 2023-03-23T08:52:50+01:00
[INFO] ------------------------------------------------------------------------
Entramos en el directorio del proyecto y probamos la ejecucion directa del plugin para comprobar que ahora si se puede ejecutar:
$ cd /home/tutorials/tmp
$ mvn com.example:amazing-maven-plugin:0.0.1-SNAPSHOT:touch
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< com.examples:project >------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default-cli) @ project ---
[INFO]  ----- I AM THE TOUCH EXECUTION -----
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.086 s
[INFO] Finished at: 2023-03-23T08:55:42+01:00
[INFO] ------------------------------------------------------------------------

9.3 EJECUCION TRANSITIVA

La ejecucion directa tiene sentido en determinadas ocasiones, pero generalmente queremos integrar los plugins de manera automatica en el ciclo de vida de un proyecto, ya sea indicandolo en el propio proyecto o en una jerarquia de proyectos (usando etiquetas ).

Para incluir un plugin en el fichero de descripcion de proyecto maven POM, basta con incluir el plugin en su seccion

Abrimos el fichero POM del proyecto que acabamos de crear e incluimos en la seccion build el plugin a usar y el goal:

    ...
    ...
    </pluginManagement>
    <plugins>
        <plugin>
            <groupId>com.example</groupId>
            <artifactId>amazing-maven-plugin</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <executions>
                <execution>
                    <goals>
                        <goal>touch</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Ahora cuando ejecutemos el ciclo de construiccion de maven, maven leera los descriptores y ejecutará ese plugin en la fase en la que se definió (si recuerdas lo hemos definido en la phase LifecyclePhase.PROCESS_CLASSES)

$ mvn clean install
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< com.examples:project >------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ project ---
[INFO] Deleting /home/tmp/project/target
[INFO] 
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ project ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/tmp/project/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ project ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/tmp/project/target/classes
[INFO] 
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default) @ project ---
[INFO]  ----- I AM THE TOUCH EXECUTION -----
[INFO] 
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ project ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
...
...
Vemos que nuestro plugin se ha ejecutado en la phase que habiamos indicado (LifecyclePhase.PROCESS_CLASSES) que es la phase despues de compile (maven-compiler-plugin).

Maven Archetype Result

10.PARAMETRIZACION DE UN MAVEN PLUGIN

Generalmente en la ejecucion de nuestro plugin necesitamos informacion acerca del proyecto donde se esta ejecutando (POM) o del contexto de ejecucion (directorio), o cualquier otro tipo de informacion extra (quizas una url donde conectar para realizar algun tipo de tarea, etc..).

Para inyectar un parametro en nuestro plugin, basta con crear una variable en nuestro Mojo y usar la annotation @Parameter.

Cambiamos el codigo por algo parecido a esto:

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

    @Parameter(property = "parameterA"  , defaultValue = "", required = true, readonly = true)
    String parameterA;


    public void execute() throws MojoExecutionException
    {
        this.getLog().info(" ----- I AM THE TOUCH EXECUTION -----");
        this.getLog().info(String.format(" ----- parameterA = %s -----",parameterA));
    }
}
Compilamos el plugin para actualizarlo mvn clean install*

10.1 Ejecucion directa inyectando el parametro

Para la ejecucion directa, inyectamos el parametro con -D

$ mvn com.example:amazing-maven-plugin:0.0.1-SNAPSHOT:touch -DparameterA=Peace

[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< com.examples:project >------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default-cli) @ project ---
[INFO]  ----- I AM THE TOUCH EXECUTION -----
[INFO]  ----- parameterA = Peace -----
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.103 s
[INFO] Finished at: 2023-03-23T14:04:01+01:00
[INFO] ------------------------------------------------------------------------

Ejecucion transitiva, parametrizamos el plugin con ese parametro en la seccion

    <plugin>
        <groupId>com.example</groupId>
        <artifactId>amazing-maven-plugin</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <configuration>
            <parameterA>StopWarUkraine</parameterA>
        </configuration>
        <executions>
            <execution>
                <goals>
                    <goal>touch</goal>
                </goals>
            </execution>
        </executions>
    </plugin>

y ejecutamos el ciclo de vida del proyecto

$ mvn clean install
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< com.examples:project >------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ project ---
[INFO] Deleting /home/dpena/development/workspaces/daniel/github/deadveloper/tmp/project/target
[INFO] 
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ project ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/dpena/development/workspaces/daniel/github/deadveloper/tmp/project/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ project ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/dpena/development/workspaces/daniel/github/deadveloper/tmp/project/target/classes
[INFO] 
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default) @ project ---
[INFO]  ----- I AM THE TOUCH EXECUTION -----
[INFO]  ----- parameterA = StopWarUkraine -----
[INFO] 
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ project ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/dpena/development/workspaces/daniel/github/deadveloper/tmp/project/src/test/resources

Si revisamos los metadatos generados por el maven-plugin-plugin podemos observar que se reflejan los parameters Maven Archetype Result

10.2 PARAMETROS CONTEXTUALES

Cuando mvn es ejecutado recopila informacion contextual relativa al proceso (por ejemplo lee el settings.xml, lee el POM, mira los MOJO que tiene disponibles para su ejecucion, etc…).

Muchos de estos parametros estan disponibles en tiempo de ejecucion y podemos inyectarlos a nuestro plugin.

Vamos a inyectar una referencia al Maven Project en el que se esta ejecutando nuestro plugin.

Para ello necesitamos editar nuestro plugin:

  1. Inyectar la lib que nos da acceso al api de lectura del POM en nuestras dependencias:
            <dependency>
                <groupId>org.apache.maven</groupId>
                <artifactId>maven-core</artifactId>
                <version>3.6.0</version>
                <scope>provided</scope>
            </dependency>   
    
  2. En nuestro mojo inyectamos una variable de tipo MavenProject y cambiamos el execute para ver como podemos explorar este objeto:

....

 import org.apache.maven.project.MavenProject;
.....
@Mojo(name = "touch", requiresProject = true, defaultPhase = LifecyclePhase.PROCESS_CLASSES)
public class MyMojo extends AbstractMojo{ 

    @Parameter(property = "parameterA"  , defaultValue = "", required = true, readonly = true)
    String parameterA;

    @Parameter(defaultValue = "${project}", required = true, readonly = true)
    MavenProject project;


    public void execute() throws MojoExecutionException
    {
        this.getLog().info(String.format(" ----- I AM THE TOUCH EXECUTION on %s %s -----", project.getArtifactId(), project.getVersion()));
        for(Dependency dep: project.getDependencies()) {
            this.getLog().info(String.format("Detected Dep: %s %s" , dep.getArtifactId(), dep.getVersion()));
        }

        this.getLog().info(String.format(" ----- parameterA = %s -----",parameterA));
    }
}
Vemos por consola la ejecucion

$ mvn clean install

[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< com.examples:project >------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ project ---
[INFO] Deleting /home/dpena/development/workspaces/daniel/github/deadveloper/tmp/project/target
[INFO] 
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ project ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/dpena/development/workspaces/daniel/github/deadveloper/tmp/project/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ project ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/dpena/development/workspaces/daniel/github/deadveloper/tmp/project/target/classes
[INFO] 
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default) @ project ---
[INFO]  ----- I AM THE TOUCH EXECUTION on project 1.0-SNAPSHOT -----
[INFO] Detected Dep: junit 4.11
[INFO]  ----- parameterA = StopWarUkraine -----
[INFO] 

Ideas: A estas alturas podriamos hacer un plugin que mire las dependencias y si existe alguna que no permitamos, lanzar una MojoExecutionException.

11.DEBUG de un MAVEN PLUGIN

Si nuestro plugin tiene una ejecucion compleja o si queremos verlo trabajando internamente en un contexto podemos usar el ejecutable mvnDebug , que internamente añade parametros de Debug remoto al ejecutarse en la maquina virtual.

El mvnDebug esperara la conexion en el puerto 8000

$ /home/.../project/$ mvnDebug clean install
Preparing to execute Maven in debug mode
Listening for transport dt_socket at address: 8000
Y en nuestro IDE favorito:

Maven Archetype Result

12.NOMENCLATURA Y ALIAS

Cada vez que ejecutamos un goal de un plugin en modo ejecucion directa, debemos indicar las coordenadas completas:

$ mvn com.example:amazing-maven-plugin:0.0.1-SNAPSHOT:touch

Quizas esta linea es muy “fea” y nos gustaria hacer algo como mvn archetype:generate

Para hacer uso de nuestros propios plugins (recuerda formato ${name}-maven-plugin) , debemos especificar el groupId de los plugins que no son del core de maven que queremos usar.

</settings>
    ...
    <pluginGroups>
    <!-- pluginGroup
     | Specifies a further group identifier to use for plugin lookup.
    <pluginGroup>com.your.plugins</pluginGroup>
    -->
        <pluginGroup>com.example</pluginGroup>
  </pluginGroups>
...

</settings>

Como hemos seguido la nomenclatura establecida ${pluginName}-maven-plugin , podemos ver que el descriptor de nuestro plugin calculó property asi:

Maven Archetype Result

Ahora podemos ejecutar directamente indicando el goalPrefix y el goal a ejecutar:

$ mvn amazing:touch -DparameterA=Peace

[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< com.examples:project >------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default-cli) @ project ---
[INFO]  ----- I AM THE TOUCH EXECUTION on project 1.0-SNAPSHOT -----
[INFO] Detected Dep: junit 4.11
[INFO]  ----- parameterA = StopWarUkraine -----
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

13. INSTALANDO EL PLUGIN EN REMOTO (RESOLUCION REMOTA)

Lo ideal del plugin es distribuirlo para que pueda ser usado por un equipo. Asi que vamos a hacer deploy del plugin y luego tratar de invocarlo.

Arrancando nexus en docker local

Arrancamos la imagen docker para hacer las pruebas:

$ docker run --rm -p 8081:8081 --name nexusLocal sonatype/nexus3
...
...
...............AdminPasswordFileManagerImpl - Writing admin user temporary password to /nexus-data/admin.password
............. AbstractConnector - Started ServerConnector@61df8c80{HTTP/1.1, (http/1.1)}{0.0.0.0:8081}
-------------------------------------------------

Started Sonatype Nexus OSS 3.49.0-02

-------------------------------------------------

Como vemos la imagen docker genera el password de admin en el archivo /nexus-data/admin.password La sacamos ejecutando

$ docker exec -it nexusLocal cat /nexus-data/admin.password
d628b7d5-81eb-4470-af9d-7eefacd00a87

Visitamos http://localhost:8081 y hacemos login (boton arriba derecho), con las credenciales admin / d628b7d5-81eb-4470-af9d-7eefacd00a87.

Maven Archetype Result

Cambiamos la password a “admin123”.

Maven Archetype Result

Y deshabilitamos acceso anonimo:

Maven Archetype Result

Configuracion de Settings.xml

Para hacer uso del repositorio nexus en local, cambiamos nuestro settings.xml con esta configuracion:

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">

  <servers>
    <server>
      <id>nexus-snapshots</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
    <server>
      <id>nexus-releases</id>
      <username>admin</username>
      <password>admin123</password>
    </server>
  </servers>

  <profiles>
    <profile>
      <repositories>
        <repository>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
          <id>nexus-releases</id>
          <name>nexus-releases</name>
          <url>http://localhost:8081/repository/maven-releases/</url>
        </repository>
        <repository>
          <snapshots />
          <id>nexus-snapshots</id>
          <name>nexus-snapshots</name>
          <url>http://localhost:8081/repository/maven-snapshots/</url>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
          <id>nexus-releases</id>
          <name>nexus-releases</name>
          <url>http://localhost:8081/repository/maven-releases/</url>
        </pluginRepository>
        <pluginRepository>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
          <id>nexus-snapshots</id>
          <name>nexus-snapshots</name>
          <url>http://localhost:8081/repository/maven-snapshots/</url>
        </pluginRepository>
      </pluginRepositories>
      <properties>
        <downloadSources>true</downloadSources>
        <downloadJavadocs>true</downloadJavadocs>
      </properties>
      <id>localNexus</id>
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>localNexus</activeProfile>
  </activeProfiles>
  <pluginGroups>
        <pluginGroup>com.example</pluginGroup>
  </pluginGroups>
</settings>

14.DEPLOY del AMAZING MAVEN PLUGIN

Editamos el pom.xml y añadimos la seccion :

    <distributionManagement>
        <repository>
            <id>nexus-releases</id>
            <url>http://localhost:8081/repository/maven-releases/</url>
        </repository>
        <snapshotRepository>
            <id>nexus-snapshots</id>
            <url>http://localhost:8081/repository/maven-snapshots/</url>
        </snapshotRepository>
    </distributionManagement>

y añadimos el nexus-staging-maven-plugin para poder hacer el deploy.

    ....
        <plugin>
            <groupId>org.sonatype.plugins</groupId>
            <artifactId>nexus-staging-maven-plugin</artifactId>
            <version>1.5.1</version>
            <executions>
                <execution>
                    <id>default-deploy</id>
                    <phase>deploy</phase>
                    <goals>
                        <goal>deploy</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <serverId>nexus</serverId>
                <nexusUrl>http://localhost:8081/nexus/</nexusUrl>
                <skipStaging>true</skipStaging>
            </configuration>
        </plugin>
    ....

Generalmente se usa maven-deploy-plugin para realizar tareas de deploy a repositorios basados en Nexus (por ejemplo artifactory Jfrog). Pero Sonatype creó el nexus-staging-maven-plugin que es mas completo y especifico para repositorios Sonatype (Nexus).

Ejecutamos la phase de deploy en el proyecto amazing-maven-plugin

mvn clean deploy -Dmaven.test.skip=true
...
...
Uploading to nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/maven-metadata.xml
Uploaded to nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/maven-metadata.xml (781 B at 11 kB/s)
Uploading to nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/maven-metadata.xml
Uploaded to nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/maven-metadata.xml (327 B at 9.9 kB/s)
Uploading to nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/maven-metadata.xml
Uploaded to nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/maven-metadata.xml (248 B at 7.3 kB/s)
[INFO]  * Bulk deploy of locally gathered snapshot artifacts finished.
[INFO] Remote deploy finished with success.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.731 s
[INFO] Finished at: 2023-03-23T17:54:50+01:00
[INFO] ------------------------------------------------------------------------

Para el proposito de esta demo debes borrar ./m2/repository/com/example despues de hacer el deploy, para asegurarnos que la siguiente vez que queramos usarlo sea descargado desde el repositorio remoto.

15. IMPORTANDO DEPENDENCIAS DEL PLUGIN

Como hemos visto , hemos configurado nuestro settings.xml indicando la seccion pluginRepositories para poder usar plugins de nuestro repositorio. Hemos dejado la seccion indicando que debe incluir los plugins de ese groupId.

      <pluginRepositories>
        <pluginRepository>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
          <id>nexus-releases</id>
          <name>nexus-releases</name>
          <url>http://localhost:8081/repository/maven-releases/</url>
        </pluginRepository>
        <pluginRepository>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
          <id>nexus-snapshots</id>
          <name>nexus-snapshots</name>
          <url>http://localhost:8081/repository/maven-snapshots/</url>
        </pluginRepository>
      </pluginRepositories>
      ....
    <pluginGroups>
        <pluginGroup>com.example</pluginGroup>
    </pluginGroups>
    ...

Si vamos al proyecto y hacemos una ejecucion directa

$ mvn amazing:touch -DparameterA=Testing
[INFO] Scanning for projects...
Downloading from central: https://repo.maven.apache.org/maven2/com/example/maven-metadata.xml
Downloading from nexus-releases: http://localhost:8081/repository/maven-releases/com/example/maven-metadata.xml
Downloading from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/maven-metadata.xml
Downloaded from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/maven-metadata.xml (248 B at 3.6 kB/s)
....
...
Downloaded from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/amazing-maven-plugin-0.0.1-20230323.165447-1.jar (4.9 kB at 182 kB/s)
[INFO] 
[INFO] ------------------------< com.examples:project >------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default-cli) @ project ---
[INFO]  ----- I AM THE TOUCH EXECUTION on project 1.0-SNAPSHOT -----
[INFO] Detected Dep: junit 4.11
[INFO]  ----- parameterA = Testing -----
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.666 s
[INFO] Finished at: 2023-03-23T18:00:53+01:00
[INFO] ------------------------------------------------------------------------

Podemos observar como trabaja Maven, explora los ficheros maven-metadata de maven central y de los groupId indicados en (en este caso los encuentra en el nexus localhost:8081) y monta una jerarquia en memoria que usará para la busqueda del Mojo que necesite en cada phase.

Si quitamos la seccion de settings.xml:

Repetimos el proceso buscando la ejecucion directa a traves del alias (no te olvides de borrar ./m2/repository/com/example que lo hemos descargado en la ejecucion anterior ):

$ mvn amazing:touch -DparameterA=Testing
[INFO] Scanning for projects...
Downloading from nexus-releases: http://localhost:8081/repository/maven-releases/org/codehaus/mojo/maven-metadata.xml
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml
Downloading from nexus-releases: http://localhost:8081/repository/maven-releases/org/apache/maven/plugins/maven-metadata.xml
Downloading from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/org/apache/maven/plugins/maven-metadata.xml
Downloading from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/org/codehaus/mojo/maven-metadata.xml
Downloading from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-metadata.xml (14 kB at 88 kB/s)
Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/mojo/maven-metadata.xml (21 kB at 189 kB/s)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.414 s
[INFO] Finished at: 2023-03-23T18:42:35+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] No plugin found for prefix 'amazing' in the current project and in the plugin groups [org.apache.maven.plugins, org.codehaus.mojo] available from the repositories [local (/home/dpena/.m2/repository), nexus-releases (http://localhost:8081/repository/maven-releases/), nexus-snapshots (http://localhost:8081/repository/maven-snapshots/), central (https://repo.maven.apache.org/maven2)] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/NoPluginFoundForPrefixException
Comprobamos que no ha ido a buscar el metadata del , solo ha buscado en los oficiales en /org/apache y /org/codehaus.

15.1 Ejecucion directa pero indicando coordenadas completas

Si por el contrario especificamos las coordenadas completas del plugin si que lo encontrara

$ mvn com.example:amazing-maven-plugin:0.0.1-SNAPSHOT:touch -DparameterA=hello
[INFO] Scanning for projects...
Downloading from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/maven-metadata.xml
Downloading from nexus-releases: http://localhost:8081/repository/maven-releases/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/maven-metadata.xml
Downloaded from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/maven-metadata.xml (781 B at 12 kB/s)
Downloading from nexus-releases: http://localhost:8081/repository/maven-releases/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/amazing-maven-plugin-0.0.1-20230323.165447-1.pom
Downloading from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/amazing-maven-plugin-0.0.1-20230323.165447-1.pom
Downloaded from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/amazing-maven-plugin-0.0.1-20230323.165447-1.pom (2.7 kB at 192 kB/s)
Downloading from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/amazing-maven-plugin-0.0.1-20230323.165447-1.jar
Downloaded from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/amazing-maven-plugin-0.0.1-20230323.165447-1.jar (4.9 kB at 328 kB/s)
[INFO] 
[INFO] ------------------------< com.examples:project >------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default-cli) @ project ---
[INFO]  ----- I AM THE TOUCH EXECUTION on project 1.0-SNAPSHOT -----
[INFO] Detected Dep: junit 4.11
[INFO]  ----- parameterA = hello -----
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.304 s
[INFO] Finished at: 2023-03-23T18:48:00+01:00
[INFO] ------------------------------------------------------------------------
Lo encuentra correctamente, porque al indicar las coordenadas, va directamente a ese metadata de GroupId.

Y tambien tendriamos resultado satisfactorio si hacemos la ejecucion transitiva, ya que la definicion en el POM.xml tiene las coordenadas completas.

        <plugin>
            <groupId>com.example</groupId>
            <artifactId>amazing-maven-plugin</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <configuration>
                <parameterA>StopWarUkraine</parameterA>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>touch</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
Ejecutamos el ciclo de vida (no te olvides de borrar ./m2/repository/com/example que lo hemos descargado en la ejecucion anterior):

$ mvn clean install
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------< com.examples:project >------------------------
[INFO] Building project 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/maven-metadata.xml
Downloading from nexus-releases: http://localhost:8081/repository/maven-releases/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/maven-metadata.xml
Downloaded from nexus-snapshots: http://localhost:8081/repository/maven-snapshots/com/example/amazing-maven-plugin/0.0.1-SNAPSHOT/maven-metadata.xml (781 B at 14 kB/s)

.....
...
[INFO] --- amazing-maven-plugin:0.0.1-SNAPSHOT:touch (default) @ project ---
[INFO]  ----- I AM THE TOUCH EXECUTION on project 1.0-SNAPSHOT -----
[INFO] Detected Dep: junit 4.11
[INFO]  ----- parameterA = StopWarUkraine -----

...
...

[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.520 s
[INFO] Finished at: 2023-03-23T18:52:33+01:00

16. CONCLUSION

RESUMEN: Si quires usar el plugin en modo ejecucion manual usando el alias, debes definir la seccion en tu settings.xml. En cualquier otro caso, como damos las coordenadas completas el plugin sera encontrado y ejecutado tanto en ejecucion directa como en ejecucion transitiva