Maven es una herramienta de gestión de todo el ciclo de vida de desarrollo de un software. El ciclo de vida básico de un software se constituye, a grandes rasgos, de unas fases básicas:

  1. Creación. Da soporte mediante Arquetipos (son plantillas para generar artefactos de software predefinidos dándonos agilidad, rapidez y uniformidad en nuestros desarrollos).
  2. Compilación. Traspaso al lenguaje máquina.
  3. Instalación. El código generado se empaqueta para ser distribuido.
  4. Despliegue. El código 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 (Official Reference).

Documento donde se muestra el lifecycle reference

De esta forma podemos ver qué 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.

Maven plugin

En cada phase del ciclo de vida se ejecuta código que realiza una determinada tarea. Estas encapsulaciones de ejecución asociadas a una determinada fase, Maven las agrupa en los llamados maven-plugin.

Un Maven plugin no es más que un fragmento de código 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.

Agentes Maven

Como hemos comentado, Maven nos ayuda en el proceso de creación de software. La fase de empaquetado del software es un proceso crucial, ya que nos permite agrupar código de una manera uniforme.

Cuando construimos software con Maven necesitamos únicamente un fichero xml llamado POM.XML (Project Object Model). Ese fichero contiene toda la metainformación que necesita la herramienta Maven para llevar a cabo todas sus tareas relacionadas con el ciclo de vida.

Dentro de este fichero encontramos la información relacionada con cuál será el modo de empaquetado de nuestro software. Se encuentra en la etiqueta .
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>

Creando un Maven plugin

Como hemos dicho, Maven nos ayuda en el proceso de creación de software, así que nos ofrece plantillas (Maven archetypes) para que podamos iniciar el desarrollo de nuestro software de una manera ágil, rápida y uniforme en nuestros desarrollos.

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
Después de la primera ejecución del arquetipo obtenemos como resultado: 
  1. Un descriptor de software Maven (POM) que indica qué se empaquetará como Maven-plugin y nos importa maven-plugin-api (que contiene las clases Core del maven-plugin-api).
EXPLORER V AMAZING-MAVEN-PLUGIN v src/ main/java/com/ example J MyMojo.java > target # pom.xml • pom.xml X Maven dependency explorer
  1. Una clase Java que extiende de AbstractMojo (maven-plugin-api dependency) con unas anotaciones en su javadoc y un código a ejecutar en su método execute();.
EXPLORER V AMAZING-MAVEN-PLUGIN y src/main/java/com/ example J MyMojo.java > target • pom.xml J MyMojo,java X src › main › java › com > example › J MyMojo.java > E MyMojo

El código fuente generado es un plugin que se ejecutará en la @phase process-sources. El nombre de esta tarea (goal) es touch y este plugin requiere un parámetro (outputDirectory) para su ejecución, que se resolverá de la expression “${project.build.directory}” (directorio del proyecto sobre el que esté actuando la ejecución de este plugin).

Si nos fijamos en la implementación del método execute, crea un fichero en el directorio en el que se encuentre.

En resumen, vemos que el maven-archetype-mojo crea un plugin cuya tarea, llamada touch (goal), se ejecutará en la phase process-sources y creará un fichero en el directorio raíz del proyecto donde se ejecuta.

Maven MOJO

Como vemos, una tarea que se ejecuta dentro de un proceso Maven es un MOJO (Maven plain Old Java Object), lo que conocemos comúnmente como un goal.

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

Es decir, podríamos crear otras clases Java en este proyecto, darles un nombre goal y asociarlas a una phase Maven y todo se empaquetaría en el mismo maven-plugin.

Fantasmas del pasado

Si ejecutamos el proceso de construcción de nuestro Maven plugin veremos que falla:

------  [INFO] [INFO] [INFO] com. example:amazing-maven-plugin >- Building amazing-maven-plugin Maven Mojo 0.0.1-SNAPSHOT --[ maven-plugin ]------ - --- ---- [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ amazing-maven-plugin --- [INFO] Deleting /tmp/tutorials/amazing-maven-plugin/target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ amazing-maven-plugin --- [WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory /tmp/tutorials/amazing-maven-plugin/src/main/resources [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ amazing-maven-plugin --- [INFO] Changes detected - recompiling the module! [WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! [INFO] Compiling 1 source file to /tmp/tutorials/amazing-maven-plugin/target/classes [INFO] [ERROR] COMPILATION ERROR [INFO] [ERROR] Source option 5 is no longer supported. Use 7 or later. [ERROR] Target option 5 is no longer supported. Use 7 or later. [INFO] 2 errors [INFO] [INFO] [INFO] .....- BUILD FAILURE

Por desgracia, la última release del maven-archetype-mojo es de 2006.

Pantallazo del mvn repository. En él, se muestran los indexed artefacts, popular categories, license, tags, rankings y versiones.

Modificaciones

Vamos a pasarlo a JAVA 17 y, para ello, 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 también se te ha hecho raro que la definición de la phase y el nombre del goal estuviera dentro de un javadoc. En 2006 seguro que tenía sentido, pero vamos a darle un lavado de cara más actual y vamos a utilizar la anotación @Mojo. Importamos la librería 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 información del javadoc para hacer uso de la anotación @Mojo, vamos a quitar el parameter y simplificamos la ejecución del método execute con el fin de que esta demo sea más 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. Para procesar este tipo de artefacto, Maven 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 está ejecutando el maven-plugin-plugin:

[INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ amazing-maven-plugin --- [INFO] Changes detected - recompiling the module! [WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! [INFO] Compiling 1 source file to /tmp/tutorials/amazing-maven-plugin/target/classes [INFO] [INFO] --- maven-plugin-plugin:3.2:descriptor (default-descriptor) @ amazing-maven-plugin --- [WARNING] Ustng ptattor™ encoding (UTF-8 actually) to read mojo metadata, i.e. build is platform dependent! [INFO] Applying mojo extractor for language: java [INFO] Mojo extractor for language: java found 0 mojo descriptors. [INFO] Applying mojo extractor for language: bsh [INFO] Mojo extractor for language: bsh found o mojo descriptors. [INFO] Applying mojo extractor for language: java-annotations [INFO] ------- [INFO] [INFO] [INFO] [INFO] BUILD FAILURE ------ Total time: 0.935 s Finished at: 2023-03-2216:06:36+01:00 ------- [INFO] [ERROR] Failed to execute goal org.apache.maven.plugins:maven-plugin-plugin:3.2:descriptor (default-descriptor) ins:maven-prugth-prugun:3.2:descriptor failed: Index 9322 out or bounds for length 89 -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.

También vemos que está ejecutando la versión 3.2 del maven-plugin-plugin en alguna fase posterior al compile.

Como hemos cambiado el procesado de la metainformación (goal, phase, etc..) y estamos haciendo uso de anotaciones, vamos a indicar específicamente en el proyecto que queremos hacer uso de una versión más actual del maven-plugin-plugin (3.8.1), que sí soporta las anotaciones.

Añadimos al pom.xml lo siguiente:

    <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 sí que podemos compilar y empaquetar correctamente:

[INFO] [INFO] maven-jar-plugin:2.4:jar (default-jar) @ amazing-maven-plugin Building jar: /tmp/tutorials/amazing-maven-plugin/target/amazing-maven-plugin-0.0.1-SNAPSHOT.jar [INFO] [INFO] maven-plugin-plugin:3.8.1:addPluginArtifactMetadata (default-addPluginArtifactMetadata) @ amazing-maven-plugin --- [INFO] [INFO] - - - maven-install-plugin:2.4:install (default-install) @ amazing-maven-plugin --- [INFO] Installing /tmp/tutorials/amazing-maven-plugin/target/amazing-maven-plugin-0.0.1-SNAPSHOT.jar to /home/dpena/.m2/repositor aven-plugin-0.0.1-SNAPSHOT. jar [INFO] Installing /tmp/tutorials/amazing-maven-plugin/pom.xml to /home/dpena/.m2/repository/com/example/amazing-maven-plugin/0.0. [INFO] [INFO] BUILD SUCCESS [INFO] [INFO] [INFO] Total time: 1.221 s Finished at: 2023-03-22T16:31:59+01:00 [INFO]

Empaquetado. ¿Qué hemos obtenido?

Del procesado del maven-plugin-plugin obtenemos un plugin en formato JAR que contiene el compilado del código a ejecutar y la metainformación que describe el plugin. Ahora vamos a analizar el resultado.

Explorando el JAR generado en /target/amazing-maven-plugin-0.0.1-SNAPSHOT.jar encontramos los .class, pero también un fichero con metainformación 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 aquí es:

  1. goalPrefix: en la resolución de goals, Maven usa una nomenclatura. Ya veremos esta property más adelante.
  2. requiredJavaVersion: versión de Java mínima requerida (ya que se compiló con java 17).
  3. requiredMavenVersion: mínima versión de Maven para ejecutar el plugin.

Y vemos que ha analizado nuestra clase anotada con @Mojo y ha creado una sección para este fichero de metainformación:

  1. touch: nombre del goal.
  2. true: indica si el plugin se puede ejecutar Standalone o si necesita un proyecto donde ejecutarse. Más adelante lo veremos.
  3. process-classes: la phase de Maven asociada para la ejecución de este goal
  4. com.example.MyMojo: código fuente.

Ejecutando un plugin

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

  1. Invocación transitiva. Nuestro plugin existe definido dentro de un proyecto (POM) y, cuando llega su phase, se ejecuta. Es el modo más común que habrás utilizado.
  2. Invocación directa. Es decir, no ejecutamos el plugin dentro del ciclo de vida, sino que lo ejecutamos a mano. Un ejemplo claro es el mvn archetype:create (estamos ejecutando el goal create del maven-archetype-plugin).

Para la invocación directa necesitamos indicar las coordenadas del artefacto Maven y el goal a ejecutar.

Vamos a un directorio vacío 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.

Requires project

Esta propiedad de la anotación @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 anotación:

@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 ejecución. 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 ejecución directa del plugin para comprobar que ahora sí 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] ------------------------------------------------------------------------

Ejecución transitiva

La ejecución directa tiene sentido en determinadas ocasiones, pero generalmente queremos integrar los plugins de manera automática en el ciclo de vida de un proyecto, ya sea indicándolo en el propio proyecto o en una jerarquía de proyectos (usando etiquetas).

Para incluir un plugin en el fichero de descripción de proyecto Maven POM, basta con incluir el plugin en su sección.

Abrimos el fichero POM del proyecto que acabamos de crear e incluimos en la sección 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 construcción de Maven, leerá 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 habíamos indicado (LifecyclePhase.PROCESS_CLASSES), que es la phase después de compile (maven-compiler-plugin).

generate-resources

Parametrización de un Maven plugin

Generalmente, en la ejecución de nuestro plugin necesitamos información acerca del proyecto donde se está ejecutando (POM), del contexto de ejecución (directorio), o cualquier otro tipo de información extra (quizás una url donde conectar para realizar algún tipo de tarea, etc.).

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

Cambiamos el código 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.

Ejecución directa inyectando el parámetro

Para la ejecución directa, inyectamos el parámetro 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] ------------------------------------------------------------------------

Ejecución transitiva, parametrizamos el plugin con ese parámetro en la sección.

    <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:

  < goal>touch Goal which touches a timestamp file. false true false < aggregator>false false true process-classes com.example.MyMojo < language>java per-lookup once-per-session false   parameterA java. lang.String true false < description/>    ${parameterA}

Parámetros contextuales

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

Muchos de estos parámetros están disponibles en tiempo de ejecución y podemos inyectarlos a nuestro plugin.

Vamos a inyectar una referencia al Maven Project en el que se está 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> 
  1. En nuestro MOJO, inyectamos una variable de tipo MavenProject y cambiamos el execute para ver cómo 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 ejecución:

$ 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] 

💡Idea: a estas alturas podríamos hacer un plugin que mire las dependencias y, si existe alguna que no permitamos, lanzar una MojoExecutionException.

Siguientes pasos

En este post hemos visto cómo podemos crear un Maven plugin y, de esta manera, realizar tareas específicas asociadas al ciclo de vida de un proyecto Maven.

Pero esto no queda aquí: próximamente veremos, en la segunda parte de este post, cómo podemos hacer debug de un Maven plugin para poder depurarlo y también cómo podemos hacerlo público para poder usarlo a través de un Alias.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.