Implementing Pre-compiled Script Plugins
A precompiled script plugin is typically a Kotlin script that has been compiled and distributed as Java class files packaged in a library. These scripts are intended to be consumed as binary Gradle plugins and are recommended for use as convention plugins.
A convention plugin is a plugin that normaly configures existing core and community plugins with your own conventions (i.e. default values) such as setting the Java version by using java.toolchain.languageVersion = JavaLanguageVersion.of(17)
.
Convention plugins are also used to enforce project standards and help streamline the build process.
They can apply and configure plugins, create new tasks and extensions, set dependencies, and much more.
Setting the plugin ID
The plugin ID for a precompiled script is derived from its file name and optional package declaration.
For example, a script named code-quality.gradle(.kts)
located in src/main/groovy
(or src/main/kotlin
) without a package declaration would be exposed as the code-quality
plugin:
plugins {
id("kotlin-dsl")
}
plugins {
id("code-quality")
}
plugins {
id 'groovy-gradle-plugin'
}
plugins {
id 'code-quality'
}
On the other hand, a script named code-quality.gradle(.kts)
located in src/main/groovy/my
(or src/main/kotlin/my
) with the package declaration my
would be exposed as the my.code-quality
plugin:
plugins {
id("kotlin-dsl")
}
plugins {
id("my.code-quality")
}
plugins {
id 'groovy-gradle-plugin'
}
plugins {
id 'my.code-quality'
}
Making a plugin configurable using extensions
Extension objects are commonly used in plugins to expose configuration options and additional functionality to build scripts.
When you apply a plugin that defines an extension, you can access the extension object and configure its properties or call its methods to customize the behavior of the plugin or tasks provided by the plugin.
A Project has an associated ExtensionContainer object that contains all the settings and properties for the plugins that have been applied to the project. You can provide configuration for your plugin by adding an extension object to this container.
Let’s update our greetings
example:
// Create extension object
interface GreetingPluginExtension {
val message: Property<String>
}
// Add the 'greeting' extension object to project
val extension = project.extensions.create<GreetingPluginExtension>("greeting")
// Create extension object
interface GreetingPluginExtension {
Property<String> getMessage()
}
// Add the 'greeting' extension object to project
def extension = project.extensions.create("greeting", GreetingPluginExtension)
You can set the value of the message
property directly with extension.message.set("Hi from Gradle,")
.
However, the GreetingPluginExtension
object becomes available as a project property with the same name as the extension object.
You can now access message
like so:
// Where the<GreetingPluginExtension>() is equivalent to project.extensions.getByType(GreetingPluginExtension::class.java)
the<GreetingPluginExtension>().message.set("Hi from Gradle")
extensions.findByType(GreetingPluginExtension).message.set("Hi from Gradle")
If you apply the greetings
plugin, you can set the convention in your build script:
plugins {
application
id("greetings")
}
greeting {
message = "Hello from Gradle"
}
plugins {
id 'application'
id('greetings')
}
configure(greeting) {
message = "Hello from Gradle"
}
Adding default configuration as conventions
In plugins, you can define default values, also known as conventions, using the project
object.
Convention properties are properties that are initialized with default values but can be overridden:
// Create extension object
interface GreetingPluginExtension {
val message: Property<String>
}
// Add the 'greeting' extension object to project
val extension = project.extensions.create<GreetingPluginExtension>("greeting")
// Set a default value for 'message'
extension.message.convention("Hello from Gradle")
// Create extension object
interface GreetingPluginExtension {
Property<String> getMessage()
}
// Add the 'greeting' extension object to project
def extension = project.extensions.create("greeting", GreetingPluginExtension)
// Set a default value for 'message'
extension.message.convention("Hello from Gradle")
extension.message.convention(…)
sets a convention for the message
property of the extension.
This convention specifies that the value of message
should default to the content of a file named defaultGreeting.txt
located in the build directory of the project.
If the message
property is not explicitly set, its value will be automatically set to the content of defaultGreeting.txt
.
Mapping extension properties to task properties
Using an extension and mapping it to a custom task’s input/output properties is common in plugins.
In this example, the message property of the GreetingPluginExtension
is mapped to the message property of the GreetingTask
as an input:
// Create extension object
interface GreetingPluginExtension {
val message: Property<String>
}
// Add the 'greeting' extension object to project
val extension = project.extensions.create<GreetingPluginExtension>("greeting")
// Set a default value for 'message'
extension.message.convention("Hello from Gradle")
// Create a greeting task
abstract class GreetingTask : DefaultTask() {
@Input
val message = project.objects.property<String>()
@TaskAction
fun greet() {
println("Message: ${message.get()}")
}
}
// Register the task and set the convention
tasks.register<GreetingTask>("hello") {
message.convention(extension.message)
}
// Create extension object
interface GreetingPluginExtension {
Property<String> getMessage()
}
// Add the 'greeting' extension object to project
def extension = project.extensions.create("greeting", GreetingPluginExtension)
// Set a default value for 'message'
extension.message.convention("Hello from Gradle")
// Create a greeting task
abstract class GreetingTask extends DefaultTask {
@Input
abstract Property<String> getMessage()
@TaskAction
void greet() {
println("Message: ${message.get()}")
}
}
// Register the task and set the convention
tasks.register("hello", GreetingTask) {
message.convention(extension.message)
}
$ gradle -q hello Message: Hello from Gradle
This means that changes to the extension’s message
property will trigger the task to be considered out-of-date, ensuring that the task is re-executed with the new message.
You can find out more about types that you can use in task implementations and extensions in Lazy Configuration.
Applying external plugins
In order to apply an external plugin in a precompiled script plugin, it has to be added to the plugin project’s implementation classpath in the plugin’s build file:
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
dependencies {
implementation("com.bmuschko:gradle-docker-plugin:6.4.0")
}
plugins {
id 'groovy-gradle-plugin'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.bmuschko:gradle-docker-plugin:6.4.0'
}
It can then be applied in the precompiled script plugin:
plugins {
id("com.bmuschko.docker-remote-api")
}
plugins {
id 'com.bmuschko.docker-remote-api'
}
The plugin version in this case is defined in the dependency declaration.