Extending Gradle with a Custom Enhanced Task: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(69 intermediate revisions by the same user not shown)
Line 1: Line 1:
=External=
* https://docs.gradle.org/current/userguide/custom_tasks.html
=Internal=
=Internal=
* [[Gradle_Task#Custom_Tasks|Gradle Tasks]]
* [[Gradle_Task#Enhanced_Task|Gradle Tasks | Enhanced Tasks]]
* [[Extending_Gradle#Custom_Task|Extending Gradle]]


=Overview=
=Overview=
An [[Gradle_Task#Enhanced_Task|enhanced task]] requires writing Groovy or Java code either in [[Extending_Gradle#In-line_in_build.gradle|in-line in build.gradle]] or [[Extending_Gradle#In-line_in_a_script_plugin|a script plugin file]], or into a source code file (or files) maintained in [[Extending_Gradle#In_the_Project.27s_buildSrc_Directory|the project's buildSrc]] or [[Extending_Gradle#External_to_Project|externally]]. The behavior is built into the task and the task exposes some properties that can be configured from the build script.
=In-Line Custom Enhanced Task=
An in-line custom enhanced task can be declared as follows, either in [[build.gradle]] or in a [[Gradle_Plugin_Concepts#Script_Plugin|script plugin]]:
<syntaxhighlight lang='groovy'>
...
class CustomEnhancedTask extends DefaultTask {
    @TaskAction
    void impl() {
        System.out.println("this is an in-line enhanced task")
    }
}
task customTask(type: CustomEnhancedTask)
...
</syntaxhighlight>
Example: {{External|[https://github.com/ovidiuf/playground/tree/master/gradle/extending-gradle/04-in-line-build-gradle-enhanced-task Custom enhanced Java task declared in-line in build.gradle]}}
This approach is not recommended as we are losing testability and reusability of the task.
=File-Based Custom Enhanced Task=


The simplest way of extending Gradle is write a custom [[Gradle_Task#Overview|task]]. The custom task can be declared [[Extending_Gradle#In-line_in_build.gradle|in-line in the default build script build.gradle]], as a [[#Simple_Task|simple task]]. The [[#Simple_Task|simple task]] can also be declared [[Extending_Gradle#In-line_in_a_script_plugin|in-line in a separate build script]], which is then included from the default build script. The code of the custom task can live in a separate source file, which in turn can be declared in [[Extending_Gradle#In_the_Project.27s_buildSrc_Directory|a special area of the Gradle project]], or can be shared with other projects as part of a library, developed in [[Extending_Gradle#External_to_Project|its own project]]. Such a task is referred to as a [[#Enhanced_Task|enhanced task]].
The same task can be defined in a Java/Groovy/Kotlin file either in the [[Extending_Gradle#In_the_Project.27s_buildSrc_Directory|project's buildSrc]] or in an [[Extending_Gradle#External_Standalone_Project|external project]]:
<syntaxhighlight lang='java'>
package playground.gradle;


Declared tasks can be listed with:
import org.gradle.api.DefaultTask;
<syntaxhighlight lang='text'>
import org.gradle.api.tasks.TaskAction;
gradle tasks --all
 
public class CustomEnhancedTask extends DefaultTask {
  @TaskAction
  void impl() {
      System.out.println("this is an custom enhanced task developed in buildSrc");
  }
}
</syntaxhighlight>
</syntaxhighlight>


=Simple Task=
If the task was defined in the [[Extending_Gradle#In_the_Project.27s_buildSrc_Directory|project's buildSrc]], then it can then be declared in build.gradle as follows. The task name is arbitrary, it does not have to match the class name, but matching names is intuitive and thus preferred.
A simple task is defined with an [[Gradle_Task#Task_Action_Closure|in-line task action closure]] in the build script or another script imported from the build script.
<syntaxhighlight lang='groovy'>
<syntaxhighlight lang='groovy'>
task customSimpleTask {
task customEnhancedTask(type: playground.gradle.CustomEnhancedTask)
    println 'this is a simple task'
}
</syntaxhighlight>
</syntaxhighlight>
Examples of Gradle projects defining [https://github.com/ovidiuf/playground/tree/master/gradle/extending-gradle/01-in-line-build-gradle-simple-task custom simple task declared in-line in build.gradle] and [https://github.com/ovidiuf/playground/tree/master/gradle/extending-gradle/02-script-plugin-simple-task custom simple task declared in-line in a script plugin].


=Enhanced Task=
Example: {{External|[https://github.com/ovidiuf/playground/tree/master/gradle/extending-gradle/03-enhanced-task-in-buildSrc Custom enhanced task developed in buildSrc]}}
An enhanced task requires writing Groovy or Java code either in [[Extending_Gradle#In-line_in_build.gradle|in-line in build.gradle]] or [[Extending_Gradle#In-line_in_a_script_plugin|a script plugin file]], or into a source code file (or files) maintained in [[Extending_Gradle#In_the_Project.27s_buildSrc_Directory|the project's buildSrc]] or [[Extending_Gradle#External_to_Project|externally]]. The behavior is built into the task and the task exposes some properties that can be configured from the build script.


An in-line custom enhanced task can be declared as follows, either in [[build.gradle]] or in a [[Gradle_Plugin_Concepts#Script_Plugin|script plugin]]:
If the task was defined in an [[Extending_Gradle#External_Standalone_Project|external project]], the external project must publish the JAR containing the task in a repository, and the Gradle project using the task must declare the JAR as dependency in the buildScript block of the build.gradle file and pull it from the repository it was published in:
<syntaxhighlight lang='groovy'>
<syntaxhighlight lang='groovy'>
...
buildscript {
class CustomEnhancedTask extends DefaultTask {
    repositories {
     @TaskAction
        mavenLocal()
     public void impl() {
     }
         System.out.println("this is an in-line enhanced task")
     dependencies {
         classpath 'playground.gradle.standalone-task:06-enhanced-task-developed-in-standalone-project:0.1.0'
     }
     }
}
}


task custEnhTask(type: CustomEnhancedTask)
task custEnhTask(type: playground.gradle.CustomEnhancedTask)
...
</syntaxhighlight>
More details on configuring the build script classpath are available in: {{Internal|Gradle Build Script Classpath|Build Script Classpath}}
Examples:
{{External|[https://github.com/ovidiuf/playground/tree/master/gradle/extending-gradle/06-enhanced-task-developed-in-standalone-project Custom enhanced task developed in standalone project]}}
{{External|[https://github.com/ovidiuf/playground/tree/master/gradle/extending-gradle/07-enhanced-task-imported-from-external-project Custom enhanced task imported from external project]}}
 
=<span id='Enhanced_Task_Implementation_Details'></span><span id='Enhanced_Custom_Task_Implementation_Details'></span>Programming Custom Tasks=
 
==@TaskAction==
{{External|[https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskAction.html org.gradle.api.tasks.TaskAction]}}
 
==Action Failure==
 
If the action code fails in any way, we indicate that the action, and thus the task failed by throwing an unchecked exception. RuntimeException works. Gradle will display the exception message:
<syntaxhighlight lang='text'>
./gradlew example
> Task :example FAILED
 
FAILURE: Build failed with an exception.
 
* What went wrong:
Execution failed for task ':example'.
> this is the RuntimeException message
[...]
</syntaxhighlight>
</syntaxhighlight>
==Logging==
{{Internal|Gradle_Logging#Accessing_Gradle_Logging_in_Custom_Java_Tasks|Accessing Gradle Logging in Custom Java Tasks}}


The same task can be defined in a Java/Groovy/Kotlin file either in the project's buildSrc or in an external project:
==Allowing Configuration via a Task Constructor==
{{External|https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:passing_arguments_to_a_task_constructor}}
Configuration values can be passed to a Task's class constructor, if the respective constructor was annotated with @javax.inject.Inject:
<syntaxhighlight lang='java'>
<syntaxhighlight lang='java'>
class CustomTask extends DefaultTask {
  final String message
  final int number
  @Inject
  CustomTask(String message, int number) {
    this.message = message
    this.number = number
  }
}
</syntaxhighlight>
</syntaxhighlight>
 
The task can then be configured with constructor arguments using the [[Gradle_Project#Task_Container|task container]], as shown below. This should be the preferred way of configuring tasks, as it improves the configuration time. For more details, see [[Gradle_Task#Task_Configuration_Avoidance_APIs|Gradle Tasks &#124; Task Configuration Avoidance APIs]].
If the task was defined in the project's buildSrc, then it can then be declared in build.gradle as follows:
<syntaxhighlight lang='groovy'>
<syntaxhighlight lang='groovy'>
tasks.create('myTask', CustomTask, 'hello', 42)
task myTask(type: CustomTask, constructorArgs: ['hello', 42])
</syntaxhighlight>
</syntaxhighlight>
In all circumstances, the values passed as constructor arguments must be non-null. If a null value is passed, Gradle will throw a NullPointerException.
==Programmatic Access to Properties==
{{Internal|Gradle_Extra_Properties#Programmatic_Access_to_Extra_Properties|Gradle Extra Properties &#124; Programmatic Access to Extra Properties}}


<font color=darkgray>If the task was defined in an external project ...</font>
==TODO==


Examples:
<font color=darkgray>
* [https://github.com/ovidiuf/playground/tree/master/gradle/extending-gradle/04-in-line-build-gradle-enhanced-task Custom ehanced Java task declared in-line in build.gradle]
The name of the method annotated with [[Gradle_Task#.40TaskAction|@TaskAction]] is arbitrary.
* [https://github.com/ovidiuf/playground/tree/master/gradle/extending-gradle/03-enhanced-task-in-buildSrc Custom enhanced task developed in buildSrc].
 
TODO:
* Gradle in Action Section 8.3.2 Using the CloudBees API from tasks.
* Gradle in Action Section 8.4 Writing custom task classes.
* https://docs.gradle.org/current/userguide/custom_tasks.html
</font>
 
=Enhanced Task Testing=
<font color=darkgray>TODO https://docs.gradle.org/current/userguide/custom_tasks.html#sec:writing_tests_for_your_task_class</font>


=TODO/TODEPLETE=
=TODO/TODEPLETE=
<font color=orange>DEPLETE [[Gradle_Task_TODEPLETE#Explicit_Task_Declaration_.28Custom_Tasks.29]]</font>
<font color=orange>DEPLETE [[Gradle_Task_TODEPLETE#Explicit_Task_Declaration_.28Custom_Tasks.29]]</font>

Latest revision as of 18:53, 10 November 2020

Internal

Overview

An enhanced task requires writing Groovy or Java code either in in-line in build.gradle or a script plugin file, or into a source code file (or files) maintained in the project's buildSrc or externally. The behavior is built into the task and the task exposes some properties that can be configured from the build script.

In-Line Custom Enhanced Task

An in-line custom enhanced task can be declared as follows, either in build.gradle or in a script plugin:

...
class CustomEnhancedTask extends DefaultTask {
    @TaskAction
    void impl() {
        System.out.println("this is an in-line enhanced task")
    }
}

task customTask(type: CustomEnhancedTask)
...

Example:

Custom enhanced Java task declared in-line in build.gradle

This approach is not recommended as we are losing testability and reusability of the task.

File-Based Custom Enhanced Task

The same task can be defined in a Java/Groovy/Kotlin file either in the project's buildSrc or in an external project:

package playground.gradle;

import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;

public class CustomEnhancedTask extends DefaultTask {
   @TaskAction
   void impl() {
      System.out.println("this is an custom enhanced task developed in buildSrc");
   }
}

If the task was defined in the project's buildSrc, then it can then be declared in build.gradle as follows. The task name is arbitrary, it does not have to match the class name, but matching names is intuitive and thus preferred.

task customEnhancedTask(type: playground.gradle.CustomEnhancedTask)

Example:

Custom enhanced task developed in buildSrc

If the task was defined in an external project, the external project must publish the JAR containing the task in a repository, and the Gradle project using the task must declare the JAR as dependency in the buildScript block of the build.gradle file and pull it from the repository it was published in:

buildscript {
    repositories {
        mavenLocal()
    }
    dependencies {
        classpath 'playground.gradle.standalone-task:06-enhanced-task-developed-in-standalone-project:0.1.0'
    }
}

task custEnhTask(type: playground.gradle.CustomEnhancedTask)

More details on configuring the build script classpath are available in:

Build Script Classpath

Examples:

Custom enhanced task developed in standalone project
Custom enhanced task imported from external project

Programming Custom Tasks

@TaskAction

org.gradle.api.tasks.TaskAction

Action Failure

If the action code fails in any way, we indicate that the action, and thus the task failed by throwing an unchecked exception. RuntimeException works. Gradle will display the exception message:

./gradlew example
> Task :example FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':example'.
> this is the RuntimeException message
[...]

Logging

Accessing Gradle Logging in Custom Java Tasks

Allowing Configuration via a Task Constructor

https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:passing_arguments_to_a_task_constructor

Configuration values can be passed to a Task's class constructor, if the respective constructor was annotated with @javax.inject.Inject:

class CustomTask extends DefaultTask {
  final String message
  final int number

  @Inject
  CustomTask(String message, int number) { 
    this.message = message
    this.number = number
  } 
}

The task can then be configured with constructor arguments using the task container, as shown below. This should be the preferred way of configuring tasks, as it improves the configuration time. For more details, see Gradle Tasks | Task Configuration Avoidance APIs.

tasks.create('myTask', CustomTask, 'hello', 42)
task myTask(type: CustomTask, constructorArgs: ['hello', 42])

In all circumstances, the values passed as constructor arguments must be non-null. If a null value is passed, Gradle will throw a NullPointerException.

Programmatic Access to Properties

Gradle Extra Properties | Programmatic Access to Extra Properties

TODO

The name of the method annotated with @TaskAction is arbitrary.

TODO:

Enhanced Task Testing

TODO https://docs.gradle.org/current/userguide/custom_tasks.html#sec:writing_tests_for_your_task_class

TODO/TODEPLETE

DEPLETE Gradle_Task_TODEPLETE#Explicit_Task_Declaration_.28Custom_Tasks.29