Por qué y ejemplo de cómo desarrollar un plugin de Gradle

Escrito por picodotdev el .
java planeta-codigo
Enlace permanente Comentarios

Generalmente cuando se necesita una funcionalidad en Gradle esta suele estar proporcionada por los propios desarrolladores de Gradle, por los propios mantenedores de otras herramientas para integrarlas en Gradle o en último caso por alguien que antes ha tenido la misma necesidad ha publicado un plugin. Si aún así no hay un plugin que ofrezca la funcionalidad que se desea, Gradle ofrece la posibilidad de que cualquiera desarrolle sus propios plugins y los use para sus necesidades específicas o en caso de ser útil para otras personas compartirlo en un repositorio público como cualquier otro.

Gradle

Java

Gradle es una herramienta de construcción y gestión del ciclo de vida de un proyecto principalmente usando en proyectos Java. Realiza importantes tareas como la descarga y gestión de las dependencias y resolución de conflictos entre las diferentes versiones que necesite una aplicación, tareas importantes que ni el sistema de módulos de Java proporciona. Permite ejecutar de forma sencilla la aplicación o las pruebas unitarias de código entre otras muchas tareas que pueden ser automatizadas como pruebas estáticas de código y generación de artefactos además de ser suficientemente flexible para adaptarse a las necesidades que haya en un proyecto.

Por todas estas importantes tareas que realiza Gradle es una herramienta imprescindible en cualquier cualquier proyecto Java. Su alternativa más usada es Maven que tiene un modelo declarativo y basado en XML con unos archivos más verbosos. Ambas herramientas se consideran ya maduras generalmente equivalentes para la mayoría de funcionalidades más comunes. Dado que los archivos de configuración de Gradle son menos verbosos, más legibles y fáciles de editar que el XML es la herramienta elegida como herramienta de construcción para muchos proyectos.

Cuando la funcionalidad ofrecida por Gradle no es suficiente, no existe un plugin de un tercero o no está adaptado a las necesidades de un proyecto u organización al igual que otros han desarrollado sus propios plugins cualquiera puede desarrolalr plugins con la interfaz que ofrece Gradle para su desarrollo y extender su funcionalidad.

Por qué desarrollar un plugin propio de Gradle

Es seguro que en una empresa a lo largo del tiempo esta desarrolle varios proyectos, siendo varios una cantidad de unas pocas decenas a cientos dependiendo del tamaño de la organización. Con múltiples proyectos por motivos de mantenimiento el copiar y pegar código entre proyectos no es una opción viable con lo que también seguro que surge la necesidad de reutilizar código propio de la organización entre los diferentes proyectos tal y como se reutiliza código de otras librerías como Spring, Hibernate y otra multitud de librerías desarrolladas por terceros. La forma de reutilizar código en Java es a través de librerías que se publican en un repositorio de Maven igual que cualquier otra librería de terceros.

También es seguro que con múltiples proyectos surge la necesidad de reutilizar código entre múltiples proyectos incluso en la herramienta de construcción. La forma que ofrece Grade de reutilizar código es a través de plugins. En el caso de la herramienta de construcción un plugin permite aplicar a todos los proyectos de forma sencilla, homogénea, mantenible y sin copiar y pegar elementos comunes considerados un estándar en la organización como comprobaciones estáticas de código con PMD, Checkstyle y SpotBugs entre otras muchas cosas propias de una organización. Muchos terceros publican plugins que cualquiera puede aplicar a un proyecto si este plugin ofrece la funcionalidad que necesita y si no existe ninguno también es posible desarrollar uno propio ajustado a las necesidades propias.

Gradle ofrece un sistema muy flexible y extensible para desarrollar, publicar y reutilizar plugins. En su documentación hay varias páginas que explican cómo desarrollar un plugin, y otros muchos artículos incluido el presente.

Por ejemplo, hace tiempo hice un ejemplo en el que generaba un archivo al hacer la construcción y generación del artefacto que incluía información del artefacto como la versión, fecha de construcción, hash del commit y número de build de modo que en tiempo de ejecución el código tenga información para mostrar esa versión en las trazas y conocer la versión exacta del código desplegado en un entorno. Otro caso puede ser que se desee una funcionalidad similar al plugin de Maven Release que permite automatizar la generación de versiones de los proyectos para la que en Gradle la opción equivalente con gradle-release su última versión es del 2017.

Conceptos de Gradle

Gradle define varios conceptos propios para la configuración y ejecución. La configuración permite cambiar el comportamiento o adaptarlo a las necesidades propias. La configuración se definen en lo que Gradle denomina extensiones que son simplemente objetos de datos en los que Gradle permite al usuario introducir datos y que en el archivo de construcción se manifiestan como bloques de configuración con propiedades, las tareas al ejecutarse obtienen las extensiones y los datos configurados en ellas, en función de los datos las tareas varían su comportamiento.

Los plugins a través de la API que ofrece Gradle pueden realizar cualquier acción que permita la API como por ejemplo aplicar nuevos plugins al proyecto, definir objetos de extensiones, definir nuevas tareas o cambiar el comportamiento de tareas existentes. Con estas funcionalidades a disposición de cualquiera es posible crear un plugin de Gradle en el que encapsular cualquier funcionalidad relativa al ciclo de vida del proyecto, aplicarla y utilizarla en cualesquiera proyectos.

Una vez creado el plugin este se publica y comparte en un repositorio de Maven del que los proyectos que lo quieran usar simplemente han de incluir la URL del repositorio y la referencia al plugin.

Ejemplo de plugin para Gradle

Lo que hace este ejemplo es mediante código aplicar cambios a través de la clase Project que representa al proyecto de Gradle. A través de esta clase se añaden nuevos plugins, se configuran las extensiones y las tareas para aplicar la funcionalidad deseada en el plugin.

El ejemplo añade los plugins y configura sus extensiones para realizar comprobaciones estáticas de código a través de las herramientas PMD, Checkstyle, SpotBugs y sus plugins para Gradle. Además, la funcionalidad del plugin es generar un archivo con información de la versión del artefacto incluyendo su versión, fecha de construcción, hash del commit y número de build para conocer esta información del artefacto.

Incluir información variable en el artefacto que es diferente en función de la fecha de ejecución hace que la generación no sea reproducible, esto es, no genere exactamente el mismo binario dado que su contenido es diferente con cada build aunque el código no haya cambiado y solo lo haya hecho un archivo de configuración y únicamente ligeramente su contenido. Que la generación de los artefactos sean reproducibles tiene el beneficio de que el artefacto generado sea auditable por motivos de seguridad y es posible que esté libre de modificaciones diferentes del código empleado para la construcción.

Crear un plugin de Gradle

Hay varias formas de desarrollar un plugin de Gradle, se puede desarrollar dentro de un proyecto existente o como un proyecto independiente, Gradle ofrece un comando para disponer de la estructura básica de un proyecto para el plugin rápidamente y de forma sencilla al igual que se realiza para disponer de una aplicación normal. En el archivo de construcción del plugin que genera Gradle incluye un plugin propio de Gradle necesario para desarrollar plugins de Gradle, java-gradle-plugin.

Los plugins de Gradle se crean proporcionando una implementación de la interfaz Plugin, esta clase ofrece el método apply que Gradle invoca para que el plugin modifique el proyecto a través de la instancia de Project que lo representa y se proporciona como argumento del método. La clase Project ofrece métodos para añadir nuevos plugins al proyecto, obtener los objetos que contienen los datos de las extensiones, permite añadir o modificar tareas existentes y crear dependencias entre las tareas. En el siguiente código se observan estas modificaciones en el proyecto en el plugin del ejemplo.

Dado que el plugin de ejemplo configura otros plugins existentes necesita de las clases de los plugins y de sus extensiones de modo que han de incluirse como una dependencia del plugin desarrollado en el archivo de construcción del plugin. El plugin también puede proporcionar sus propias clases de extensiones.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
package io.github.picodotdev.blogbitix.javagradleplugin;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import com.github.spotbugs.snom.SpotBugsExtension;
import com.github.spotbugs.snom.SpotBugsPlugin;
import org.apache.tools.ant.filters.ReplaceTokens;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.file.DuplicatesStrategy;
import org.gradle.api.java.archives.Manifest;
import org.gradle.api.plugins.JavaApplication;
import org.gradle.api.plugins.quality.CheckstyleExtension;
import org.gradle.api.plugins.quality.CheckstylePlugin;
import org.gradle.api.plugins.quality.PmdExtension;
import org.gradle.api.plugins.quality.PmdPlugin;
import org.gradle.api.tasks.Copy;
import org.gradle.api.tasks.bundling.Jar;

public class JavaGradlePlugin implements Plugin<Project> {

    private static final String SPOTBUGS_EXCLUDE_RESOURCE = "/spotbugs/spotbugs-exclude.xml";
    private static final String CHECKSTYLE_CONFIG_RESOURCE = "/checkstyle/checkstyle.xml";
    private static final String CHECKSTYLE_SUPPRESSIONS_RESOURCE = "/checkstyle/suppressions.xml";

    public void apply(Project project) {
        project.getPluginManager().apply(PmdPlugin.class);
        project.getPluginManager().apply(CheckstylePlugin.class);
        project.getPluginManager().apply(SpotBugsPlugin.class);

        JavaGradlePluginExtension javaGradlePluginExtension = project.getExtensions().create("version", JavaGradlePluginExtension.class);
        javaGradlePluginExtension.getVersionEnabled().convention(Boolean.FALSE);

        PmdExtension pmdExtension = project.getExtensions().getByType(PmdExtension.class);
        pmdExtension.setToolVersion("6.44.0");
        pmdExtension.setConsoleOutput(true);
        pmdExtension.setIgnoreFailures(false);
        pmdExtension.setRuleSets(List.of("category/java/bestpractices.xml"));

        CheckstyleExtension checkstyleExtension = project.getExtensions().getByType(CheckstyleExtension.class);
        checkstyleExtension.setConfigFile(getFileFromResource(project, CHECKSTYLE_CONFIG_RESOURCE));
        checkstyleExtension.setConfigProperties(Map.of("suppressionFile", getFileFromResource(project, CHECKSTYLE_SUPPRESSIONS_RESOURCE)));
        checkstyleExtension.setMaxWarnings(0);
        checkstyleExtension.setMaxErrors(0);

        SpotBugsExtension spotBugsExtension = project.getExtensions().getByType(SpotBugsExtension.class);
        spotBugsExtension.getToolVersion().convention("4.2.2");
        spotBugsExtension.getShowProgress().convention(true);
        spotBugsExtension.getIgnoreFailures().convention(true);
        spotBugsExtension.getExcludeFilter().convention(() -> getFileFromResource(project, SPOTBUGS_EXCLUDE_RESOURCE));

        project.getTasks().named("processResources", Copy.class).configure(copy -> {
            copy.from("src/main/resources", (itCopySpec) -> {
                itCopySpec.setDuplicatesStrategy(DuplicatesStrategy.INCLUDE);
                itCopySpec.from("src/main/resources", (itFrom) -> {
                    itFrom.include("banner.txt");
                    itFrom.exclude("version.properties");
                });
                Map<String, String> tokens = new HashMap<>();
                tokens.put("gradle.app.name", project.getName());
                tokens.put("gradle.version.version", getPluginVersion(project).get().toString());
                tokens.put("gradle.version.build", getPluginVersion(project).get().getBuild());
                tokens.put("gradle.version.hash", getPluginVersion(project).get().getHash());
                tokens.put("gradle.version.timestamp", getPluginVersion(project).get().getTimestamp());
                tokens.put("gradle.version.string", getPluginVersion(project).get().getString());
                tokens.put("gradle.version.formatted-string", getPluginVersion(project).get().getFormattedString());
                itCopySpec.filter(Map.of("beginToken", "${", "endToken", "}", "tokens", tokens), ReplaceTokens.class);
            });
        });

        project.getTasks().named("jar", Jar.class).configure(jar -> {
            jar.manifest((Manifest manifest) -> {
                Map<String, String> attributes = new HashMap<>();
                attributes.put("Class-Path", project.getConfigurations().getByName("runtimeClasspath").getFiles().stream().map(it -> String.format("files/%s", it.getName())).collect(
                        Collectors.joining(" ")));
                if (project.getExtensions().getByType(JavaApplication.class).getMainClass().isPresent()) {
                    attributes.put("Main-Class", project.getExtensions().getByType(JavaApplication.class).getMainClass().get());
                }
                attributes.put("App-Name", project.getName());
                if (getPluginVersion(project).isPresent()) {
                    attributes.put("App-Version", getPluginVersion(project).get().toString());
                }
                manifest.attributes(attributes);
            });
        });
    }

    private File getFileFromResource(Project project, String resource) {
        return project.getResources().getText().fromUri(getClass().getResource(resource)).asFile();
    }

    private Optional<Version> getPluginVersion(Project project) {
        if (project.getVersion() instanceof Version) {
            return Optional.of((Version) project.getVersion());
        }
        return Optional.empty();
    }
}
JavaGradlePlugin.java
1
2
3
4
5
6
7
8
9
package io.github.picodotdev.blogbitix.javagradleplugin;

import org.gradle.api.provider.Property;

public interface JavaGradlePluginExtension {

  Property<Boolean> getCheckstyleEnabled();
  Property<Boolean> getVersionEnabled();
}
JavaGradleExtension.java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
package io.github.picodotdev.blogbitix.javagradleplugin;

import java.nio.charset.Charset;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class Version {

    private String version;
    private String build;
    private String hash;
    private String timestamp;

    public Version() {
        this(Collections.emptyMap());
    }

    public Version(Map<String, String> properties) {
        this.version = properties.getOrDefault("version", "0.0.0");
        this.build = properties.getOrDefault("build", System.getenv("BUILD_NUMBER"));
        this.hash = properties.getOrDefault("hash", getGitHash());
        this.timestamp = properties.getOrDefault("timestamp", DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ssX").format(ZonedDateTime.now(ZoneId.of("UTC"))));
        if (this.build == null) {
            this.build = "0";
        }
        if (this.hash == null) {
            this.hash = "0000000";
        }
    }

    public String getVersion() {
        return version;
    }

    public int getVersionMajor() {
        String n = version.split(".")[0];
        return Integer.parseInt(n);
    }

    public int getVersionMinor() {
        String n = version.split(".")[1];
        return Integer.parseInt(n);
    }

    public int getVersionPatch() {
        String n = version.split(".")[2];
        return Integer.parseInt(n);
    }

    public String getBuild() {
        return build;
    }

    public String getHash() {
        return hash;
    }

    public String getTimestamp() {
        return timestamp;
    }

    public String getString() {
        List<String> strings = new ArrayList<>();
        strings.add(String.format("v%s", version));
        strings.add(String.format("b%s", build));
        strings.add(String.format("h%s", hash));
        strings.add(String.format("d%s", timestamp));
        return strings.stream().collect(Collectors.joining("-"));
    }

    public String getFormattedString() {
        return String.format(" (%s)", getString());
    }

    @Override
    public String toString() {
        return version;
    }

    private String getGitHash() {
        try {
            Process process = Runtime.getRuntime().exec("git log -n 1 --format=%h");
            process.waitFor(5000, TimeUnit.SECONDS);
            if (process.exitValue() != 0) {
                return null;
            }

            return new String(process.getInputStream().readAllBytes(), Charset.forName("UTF-8")).trim();
        } catch (Exception e) {
            return null;
        }
    }
}
Version.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
plugins {
    id 'java-gradle-plugin'
    id 'maven-publish'
}

project.group = 'io.github.picodotdev.blogbitix.javagradleplugin'
project.version = "1.0"

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

repositories {
    mavenCentral()
    gradlePluginPortal()
}

dependencies {
    implementation 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.6'
    implementation 'com.puppycrawl.tools:checkstyle:10.1'
    implementation 'net.sourceforge.pmd:pmd:6.44.0'
}

gradlePlugin {
    plugins {
        javaGradlePlugin {
            id = 'io.github.picodotdev.blogbitix.javagradleplugin'
            implementationClass = 'io.github.picodotdev.blogbitix.javagradleplugin.JavaGradlePlugin'
        }
    }
}
build.gradle

Los plugins de Gradle se publican en un repositorio de Maven como cualquier librería o aplicación, Gradle compila el plugin y lo publica. Para desarrollar es posible publicar el plugin en el repositorio de Maven local.

1
2
$ ./gradlew publishToMavenLocal

gradlew-publishtomavenlocal.sh

También es posible realizar teses unitarios y funcionales sobre el plugin, al utilizar la tarea de creación del proyecto del plugin Gradle crea unos ejemplos en los basarse para crear más.

Aplicar el plugin de Gradle a un proyecto

Una vez el plugin está en el repositorio de Maven usar el plugin y aplicarlo a un proyecto es exactamente igual que cualquier otro plugin de Gradle, basta incluirlo en la sección plugins con su identificador y su versión. Una vez aplicado el plugin como este añade otros plugins al ejecutar la tareas tasks que muestra las tareas existentes en el proyecto se observa que aunque los plugins de PMD, Checkstyle, SpotBugs han incluido sus tareas en el proyecto aunque no se hayan aplicado de forma explícita sino de forma transitiva a través del plugin propio.

1
2
$ ./gradlew tasks

gradlew-tasks.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
> Task :tasks

------------------------------------------------------------
Tasks runnable from root project 'java-application'
------------------------------------------------------------

Application tasks
-----------------
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles test classes.

Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.

Distribution tasks
------------------
assembleDist - Assembles the main distributions
distTar - Bundles the project as a distribution.
distZip - Bundles the project as a distribution.
installDist - Installs the project as a distribution as-is.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the main source code.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in root project 'java-application'.
dependencies - Displays all dependencies declared in root project 'java-application'.
dependencyInsight - Displays the insight into a specific dependency in root project 'java-application'.
help - Displays a help message.
javaToolchains - Displays the detected java toolchains.
outgoingVariants - Displays the outgoing variants of root project 'java-application'.
projects - Displays the sub-projects of root project 'java-application'.
properties - Displays the properties of root project 'java-application'.
tasks - Displays the tasks runnable from root project 'java-application' (some of the displayed tasks may belong to subprojects).

Verification tasks
------------------
check - Runs all checks.
spotbugsMain - Run SpotBugs analysis for the source set 'main'
spotbugsTest - Run SpotBugs analysis for the source set 'test'
test - Runs the test suite.

To see all tasks and more detail, run gradlew tasks --all

To see more detail about a task, run gradlew help --task <task>

BUILD SUCCESSFUL in 628ms
1 actionable task: 1 executed
gradlew-tasks.out

Ejecutando la build la tarea se genera el archivo como un recurso que se procesa al que se le realizan varias sustituciones con los valores que permite en tiempo de ejecución conocer información de la versión de la librería o aplicación, este archivo se incluye en el jar de la librería o aplicación y es posible acceder a él como un recurso más.

1
2
3
4
5
6
7
app.version.name=app
app.version.version=1.0.0
app.version.build=0
app.version.hash=5f9b859
app.version.timestamp=2022-04-07-17-41-30Z
app.version.string=v1.0.0-b0-h5f9b859-d2022-04-07-17-41-30Z
app.version.formatted-string= (v1.0.0-b0-h5f9b859-d2022-04-07-17-41-30Z)
version.properties
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package io.github.picodotdev.blogbitix.javaapplication;

import java.io.InputStream;
import java.util.Properties;

public class App {

    public static void main(String[] args) throws Exception {
        InputStream inputStream = App.class.getResourceAsStream("/version.properties");

        Properties properties = new Properties();
        properties.load(inputStream);

        System.out.println(properties.getProperty("app.version.formatted-string"));

    }
}
App.java
1
2
$ ./gradlew run

gradlew-run.sh
1
2
3
4
5
> Task :app:run
(v1.0.0-b0-h5f9b859-d2022-04-07-17-41-30Z)

BUILD SUCCESSFUL in 614ms
3 actionable tasks: 1 executed, 2 up-to-date
gradlew-run.out

Al ejecutar la tarea de build dado que se han incluido los plugins PMD, CheckStyle y Spotbugs sus tareas de validación se ejecutan y detectan los errores en los archivos del código fuente según las reglas de validación configuradas. En el código de ejemplo Checkstyle indica que hay el código no está formateado de forma apropiada siguiendo las convenciones establecidas para el proyecto.

1
2
$ ./gradlew build

gradlew-build.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
> Task :app:checkstyleMain FAILED
[ant:checkstyle] [WARN] /home/picodotdev/Documentos/Software/personal/blog-ejemplos/GradlePlugin/JavaApplication/app/src/main/java/io/github/picodotdev/blogbitix/javaapplication/App.java:8:5: method def modifier en el nivel de sangrado 4 no está al nivel correcto, 2 [Indentation]
[ant:checkstyle] [WARN] /home/picodotdev/Documentos/Software/personal/blog-ejemplos/GradlePlugin/JavaApplication/app/src/main/java/io/github/picodotdev/blogbitix/javaapplication/App.java:9:9: method def el descendiente en el nivel de sangrado 8 no está al nivel correcto, 4 [Indentation]
[ant:checkstyle] [WARN] /home/picodotdev/Documentos/Software/personal/blog-ejemplos/GradlePlugin/JavaApplication/app/src/main/java/io/github/picodotdev/blogbitix/javaapplication/App.java:11:9: method def el descendiente en el nivel de sangrado 8 no está al nivel correcto, 4 [Indentation]
[ant:checkstyle] [WARN] /home/picodotdev/Documentos/Software/personal/blog-ejemplos/GradlePlugin/JavaApplication/app/src/main/java/io/github/picodotdev/blogbitix/javaapplication/App.java:12:9: method def el descendiente en el nivel de sangrado 8 no está al nivel correcto, 4 [Indentation]
[ant:checkstyle] [WARN] /home/picodotdev/Documentos/Software/personal/blog-ejemplos/GradlePlugin/JavaApplication/app/src/main/java/io/github/picodotdev/blogbitix/javaapplication/App.java:14:9: method def el descendiente en el nivel de sangrado 8 no está al nivel correcto, 4 [Indentation]
[ant:checkstyle] [WARN] /home/picodotdev/Documentos/Software/personal/blog-ejemplos/GradlePlugin/JavaApplication/app/src/main/java/io/github/picodotdev/blogbitix/javaapplication/App.java:16:5: method def rcurly en el nivel de sangrado 4 no está al nivel correcto, 2 [Indentation]

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:checkstyleMain'.
> Checkstyle rule violations were found. See the report at: file:///home/picodotdev/Documentos/Software/personal/blog-ejemplos/GradlePlugin/JavaApplication/app/build/reports/checkstyle/main.html
  Checkstyle files with violations: 1
  Checkstyle violations by severity: [warning:6]


* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 1s
7 actionable tasks: 5 executed, 2 up-to-date
gradlew-build.out
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew build


Comparte el artículo: