2024-07-30
"To end the chain of despair, I hope she can become a light that illuminates the right path." – "Re:Zero - Starting Life in Another World" · Kuer
Gradle Closure Example
Here’s a breakdown of that Java method in your Gradle plugin, written using Groovy DSL. This method takes advantage of Groovy’s Closure and Delegate mechanisms. Let’s dive into each part of this code:
public void forcedTypes(@DelegatesTo(ForcedTypesHandler.class) Closure<?> closure) {
closure.setDelegate(this.forcedTypes);
closure.setResolveStrategy(1);
closure.call();
}
Breaking it Down
-
Method Signature
public void forcedTypes(@DelegatesTo(ForcedTypesHandler.class) Closure<?> closure)
public void forcedTypes(...)
: This defines a public method namedforcedTypes
with no return value.@DelegatesTo(ForcedTypesHandler.class) Closure<?> closure
: The parameter is a Groovy Closure, and the@DelegatesTo
annotation specifies that the delegate for this closure should be of typeForcedTypesHandler
.
-
Setting the Closure's Delegate
closure.setDelegate(this.forcedTypes);
closure.setDelegate(...)
: This sets the delegate of the closure tothis.forcedTypes
, meaning it will delegate method calls to thisforcedTypes
object.
-
Setting the Closure's Resolve Strategy
closure.setResolveStrategy(1);
closure.setResolveStrategy(...)
: This sets the closure's resolve strategy to 1, which corresponds toDELEGATE_FIRST
. This means the closure will look for properties and methods on the delegate object first.
-
Calling the Closure
closure.call();
closure.call()
: This executes the closure.
What’s the Point?
This kind of code is typical in Gradle plugins to allow users to configure forcedTypes
in a more intuitive way using DSL. When a user calls forcedTypes { ... }
in their Gradle script, the closure they pass in is dynamically bound to the forcedTypes
object. This allows users to directly configure forcedTypes
within the closure, like this:
forcedTypes {
type {
name = 'VARCHAR'
expression = '.*'
}
}
In this example, the configuration inside the forcedTypes
closure applies directly to the forcedTypes
object, making the setup process simpler.
Adding Dependencies Dynamically in a Gradle Plugin
Here's some code that dynamically adds dependencies in a Gradle plugin. It does this by calling project.getDependencies().addProvider
, which adds a dependency to a specific configuration. Let’s break it down:
project.getDependencies().addProvider(
sourceSet.getImplementationConfigurationName(),
jooqExtension.getEdition().map(e -> e.getGroupId() + ":jooq")
.flatMap(ga -> jooqExtension.getVersion().map(v -> ga + ":" + v))
);
Breaking it Down
-
Getting the Configuration Name
sourceSet.getImplementationConfigurationName()
sourceSet.getImplementationConfigurationName()
: This gets the implementation configuration name for thesourceSet
.sourceSet
typically refers to a source set in the Gradle project (likemain
,test
, etc.), and the implementation configuration name is something likeimplementation
(e.g.,mainImplementation
).
-
Building the Dependency String
jooqExtension.getEdition().map(e -> e.getGroupId() + ":jooq")
.flatMap(ga -> jooqExtension.getVersion().map(v -> ga + ":" + v))jooqExtension.getEdition()
: Gets theEdition
property fromjooqExtension
, which is aProvider
object..map(e -> e.getGroupId() + ":jooq")
: Maps theEdition
object to a string combining itsGroupId
and"jooq"
..flatMap(ga -> jooqExtension.getVersion().map(v -> ga + ":" + v))
: Further maps this to combine it with theVersion
property, forming a complete dependency string likeorg.jooq:jooq:3.14.8
.
-
Adding the Dependency
project.getDependencies().addProvider(...);
project.getDependencies().addProvider
: This Gradle method adds a dynamically provided dependency to the specified configuration.- The first argument is the configuration name obtained from
sourceSet.getImplementationConfigurationName()
. - The second argument is the dependency coordinate string, dynamically generated from
jooqExtension
'sEdition
andVersion
properties.
Where Does the Dependency Go?
The dependencies added here are for the project managed by the plugin, not the plugin itself. Specifically:
project.getDependencies().addProvider
adds the dependency to the specified configuration of the project using the plugin.sourceSet
represents the source set of the project using the plugin, so the dependency is added to that project’s implementation configuration.
Wrapping Up the Dependency Add
In summary, this code dynamically gets the Edition
and Version
properties from jooqExtension
, creates the dependency coordinate string, and adds this dependency to the implementation configuration of the source set in the project using the plugin. This way, users can configure their project dependencies flexibly via the jooqExtension
without manually adding them in their build scripts.
Gradle Property.finalizeValueOnRead()
In Gradle, the Property
interface provides a flexible, type-safe way to handle properties. The finalizeValueOnRead()
method is part of the Property
interface and controls the lifecycle and mutability of the property value.
Breaking it Down
private final Property<String> version;
private final Property<JooqEdition> edition;
- These lines define two private, final properties,
version
andedition
. They are of typeProperty
, which is a Gradle interface for handling properties.
version.finalizeValueOnRead();
edition.finalizeValueOnRead();
finalizeValueOnRead()
is called on these properties. This method fixes the property value upon first read, making it immutable afterward.
What's finalizeValueOnRead()
About?
The main job of finalizeValueOnRead()
is to control the immutability of a property. Specifically:
- Fixing the Property Value: Once the property value is read, it’s locked in (finalized). This means after the first read, you can’t change it anymore.
- Ensuring Consistency: In some cases, you want to ensure that once a property value is used, it remains consistent and doesn’t change. This helps maintain consistent configuration and prevents potential issues from concurrent modifications.
- Preventing Subsequent Modifications: After calling
finalizeValueOnRead()
, any attempt to modify the property value after it has been read will throw an exception. This helps catch and prevent unintended changes.
Use Cases
This method is typically used in scenarios like:
- Lazy Initialization: The property value might be dynamically computed or set during plugin or task execution. Before it’s read, it can be mutable.
- Configuration Locking: Once configuration values are read, lock them to prevent further changes, ensuring the task or plugin behavior remains predictable.
- Ensuring Safety: In a multithreaded environment, ensure a property, once read, isn’t modified by other threads, avoiding concurrency issues.
Example
public class MyPluginExtension {
private final Property<String> version;
private final Property<JooqEdition> edition;
@Inject
public MyPluginExtension(ObjectFactory objects) {
this.version = objects.property(String.class);
this.edition = objects.property(JooqEdition.class);
version.finalizeValueOnRead();
edition.finalizeValueOnRead();
}
// Getter and setter methods
public Property<String> getVersion() {
return version;
}
public Property<JooqEdition> getEdition() {
return edition;
}
}
In this example:
version
andedition
are properties whose values are fixed upon first read.version.finalizeValueOnRead();
andedition.finalizeValueOnRead();
ensure that these properties’ values cannot be changed after being read for the first time.
Wrapping It Up
The finalizeValueOnRead()
method fixes the property value upon first read to ensure consistency and stability throughout the build process. This mechanism helps prevent unexpected changes in configuration at runtime, making plugin or task behavior predictable and reliable.