2024-07-29
Three thousand lanterns light up the long night, Illuminating the Heavenly Gate and the snowy thousand mountains. --- "Millennium of Candlelight" · Millennium of Candlelight
Gradle Plugin
org.gradle.toolchains.foojay-resolver-convention
The org.gradle.toolchains.foojay-resolver-convention
plugin is a Gradle plugin designed to simplify the resolution and configuration of Java toolchains. This plugin leverages services provided by Foojay (Friends of OpenJDK) to automatically resolve and download the required JDK versions, thus simplifying the Java environment configuration for a project.
How the Plugin Works
- Toolchain Configuration: Specify the required Java version and vendor in the
build.gradle
file. - Automatic Download: If the specified JDK is not found locally during the build process, the plugin will automatically download it from the Foojay service.
- Environment Configuration: Once downloaded, the plugin will configure the JDK for the project.
Usage Example
Here is an example configuration that demonstrates how to use the plugin to automatically resolve and download the JDK:
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17) // Specify the required Java version
}
}
tasks.withType(JavaCompile).configureEach {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(17)
}
}
Explanation
- Plugin Declaration: Declare the
org.gradle.toolchains.foojay-resolver-convention
plugin in theplugins
block. - Toolchain Configuration: Configure the required Java version in the
java
block, specifying Java 17 in this example. - Task Configuration: Specify the automatically resolved Java compiler for all
JavaCompile
tasks.
Testing Automatic Download
You can test the plugin's automatic download and configuration feature by running the following command in an environment without the pre-installed specified JDK:
./gradlew compileJava
During the build process, Gradle will check if the required JDK is available locally. If not, the plugin will automatically download and configure it.
Additional Configuration Options
You can also specify more options, such as the JDK vendor:
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
vendor = JvmVendorSpec.ADOPTIUM // Specify JDK vendor as Adoptium
}
}
This way, you can ensure that all development and build environments use the same JDK version and vendor, reducing issues caused by environmental differences. If you have any other questions or need further assistance, please let me know!
Reference Documentation
For more information on Java toolchains and Foojay services, refer to the Gradle Official Documentation and Foojay Official Website.
Forcing Specific Dependency Version in Gradle
This snippet forces a specific version of the jakarta.xml.bind:jakarta.xml.bind-api
dependency to version 3.0.1
through Gradle's dependency resolution strategy. Here's a detailed explanation:
Detailed Configuration
configurations.all { Configuration c ->
c.resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'jakarta.xml.bind' && details.requested.name == 'jakarta.xml.bind-api') {
details.useVersion '3.0.1'
}
}
}
Key Points
- Iterate Over All Configurations:
configurations.all
iterates over all configurations in the project (e.g.,compileClasspath
,runtimeClasspath
, etc.). - Set Resolution Strategy:
c.resolutionStrategy.eachDependency
sets a resolution strategy for each dependency. - Check Dependency:
DependencyResolveDetails
object contains information about the current dependency. You can get the group and name of the dependency throughdetails.requested.group
anddetails.requested.name
. - Force Specific Version:
details.useVersion '3.0.1'
forces the version of thejakarta.xml.bind:jakarta.xml.bind-api
dependency to3.0.1
.
Explanation
configurations.all { Configuration c -> ... }
: Applies the same dependency resolution strategy to all configurations.c.resolutionStrategy.eachDependency { DependencyResolveDetails details -> ... }
: Configures a resolution strategy for each dependency.if (details.requested.group == 'jakarta.xml.bind' && details.requested.name == 'jakarta.xml.bind-api') { ... }
: Checks if the current dependency being resolved isjakarta.xml.bind:jakarta.xml.bind-api
.details.useVersion '3.0.1'
: Forces the version of the matching dependency to3.0.1
.
Use Cases
This code is particularly useful in the following scenarios:
- Resolving Version Conflicts: When multiple libraries depend on different versions of
jakarta.xml.bind-api
, you can use this approach to force a single version, avoiding conflicts. - Ensuring Version Consistency: Ensures that all configurations in the project use the same version of
jakarta.xml.bind-api
, improving consistency. - Dependency Upgrades: When you need to upgrade a specific dependency version across the project without modifying each dependency declaration individually, you can use this approach to apply the upgrade uniformly.
Summary
By using this code, you can ensure that all jakarta.xml.bind:jakarta.xml.bind-api
dependencies in the project use version 3.0.1
. This method is an effective way to manage dependency version consistency and resolve version conflicts.
Configuring Compiler Behavior in Gradle
This code configures compiler options for all tasks of type AbstractCompile
. Specifically, it adds the -Werror
and -Xlint:all
compiler arguments, which affect the behavior of the Java compiler.
Code Explanation
tasks.withType(AbstractCompile).configureEach {
options.compilerArgs <<
"-Werror" <<
"-Xlint:all"
}
Key Points
- Iterate Over Compile Tasks:
tasks.withType(AbstractCompile).configureEach
iterates over all tasks of typeAbstractCompile
(e.g.,JavaCompile
,GroovyCompile
, etc.), configuring each one. - Set Compiler Arguments:
options.compilerArgs
provides a list to which you can add compiler arguments. This code adds the-Werror
and-Xlint:all
arguments to this list.
Compiler Argument Explanation
-Werror
: Treats all warnings as errors. If any warnings are encountered during compilation, the build will fail. This helps ensure code quality by forcing developers to resolve all warnings.-Xlint:all
: Enables all compiler warnings. The compiler will report all potential issues, such as unused variables, unhandled exceptions, etc. This helps developers identify and fix potential issues early.
Use Cases
This code is particularly useful in the following scenarios:
- Improving Code Quality: By treating warnings as errors, it ensures that code compiles without any warnings, helping to maintain high code quality.
- Strict Code Review: In team development, forcing the resolution of all warnings helps team members write more standardized and safer code.
- Identifying Potential Issues: Enabling all warnings helps developers identify potential issues in the code, allowing them to fix these issues early in the development process.
Explanation
tasks.withType(AbstractCompile)
: Gets all tasks of typeAbstractCompile
.configureEach
: Configures each task.options.compilerArgs
: Gets the compiler argument list.<<
: Adds new compiler arguments to the list (-Werror
and-Xlint:all
).
Complete Example
Assuming your project uses the Java compiler, here is a complete build.gradle
file example:
plugins {
id 'java'
}
tasks.withType(AbstractCompile).configureEach {
options.compilerArgs << "-Werror" << "-Xlint:all"
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'junit:junit:4.13.2'
}
Effect
By configuring this way, all compile tasks will treat warnings as errors and enable all warnings, thus improving code quality and safety.
Configuring Build Scan Values in Gradle
These two lines of code utilize Gradle's build scan feature to record the JVM version and Gradle version used during the build process. Build scans are powerful tools that capture and analyze build data, helping developers understand and optimize the build process. Specifically, these lines of code add the JVM version and Gradle version information to the build scan for later viewing and analysis.
Code Explanation
buildScan.value(identityPath.path + "#jvmVersion", testJavaRuntimeVersion)
buildScan.value(identityPath.path + "#gradleVersion", testGradleVersion)
Detailed Explanation
-
buildScan.value(key, value)
: This is a method provided by the Gradle build scan API to add custom values to the build scan.key
is the key for the custom value, andvalue
is the content of the custom value. -
identityPath.path + "#jvmVersion"
andidentityPath.path + "#gradleVersion"
:identityPath.path
: This is a built-in Gradle property representing the identity path of the current task or project. It is often used to generate unique keys
to ensure that build scan data does not conflict across different tasks or projects.
#jvmVersion
and#gradleVersion
: These are custom suffixes used to distinguish the recorded values, representing the JVM version and Gradle version, respectively.
testJavaRuntimeVersion
andtestGradleVersion
:testJavaRuntimeVersion
: This is a string representing the Java runtime version used for testing. This value is obtained from project properties or default values in the preceding code.testGradleVersion
: This is a string representing the Gradle version used for testing. This value is also obtained from project properties or the current Gradle version in the preceding code.
Purpose and Significance
By adding the JVM version and Gradle version information to the build scan, you can view these details in the build scan report after the build is complete. This is particularly useful in the following situations:
- Debugging and Troubleshooting: When a build fails or encounters issues, knowing the JVM and Gradle versions used can help quickly pinpoint the root cause.
- Performance Analysis: When analyzing build performance, knowing the environment information (such as JVM and Gradle versions) can help optimize the build process.
- Build History Tracking: Recording and tracking the versions used can help maintain build consistency and reproducibility.
Example Explanation
Suppose your build scan report includes the following information:
- Task Path:
:compileJava
- JVM Version:
21
- Gradle Version:
7.4
In the build scan, you might see custom values like the following:
:compileJava#jvmVersion
:21
:compileJava#gradleVersion
:7.4
These details help you clearly see the JVM and Gradle versions used for each task in the build scan report, aiding in better understanding and optimizing the build process.
Configuring Javadoc Options in Gradle
This code configures additional Javadoc options for all tasks of type Javadoc
. Specifically, it adds the -Xdoclint:none
and -quiet
options. Here is a detailed explanation:
Code Explanation
tasks.withType(Javadoc).configureEach {
options.addStringOption('Xdoclint:none', '-quiet')
}
Key Points
- Iterate Over Javadoc Tasks:
tasks.withType(Javadoc).configureEach
iterates over all tasks of typeJavadoc
, configuring each one. - Configure Javadoc Options: Adds specific options to the Javadoc tasks using the
options.addStringOption
method.
Javadoc Options Explanation
-Xdoclint:none
: Disables all Javadoc lint checks. This means that when generating Javadoc, no strict checks will be performed on the content of the comments, such as format, spelling, and tag usage. This is useful for quickly generating Javadoc or generating documentation when comments are incomplete.-quiet
: Minimizes the output from the Javadoc tool during document generation. This reduces noise in the build logs, especially for large projects.
Use Cases
This code is particularly useful in the following scenarios:
- Quickly Generating Javadoc: Quickly generating Javadoc without strict checks on the comment content.
- Reducing Build Log Noise: Using the
-quiet
option to reduce output during Javadoc generation, making the build logs cleaner. - Resolving Existing Comment Issues: If there are many incomplete or improperly formatted Javadoc comments in the project, disabling lint checks can prevent build failures or numerous warnings.
Explanation
tasks.withType(Javadoc)
: Gets all tasks of typeJavadoc
.configureEach
: Configures each task.options.addStringOption('Xdoclint:none', '-quiet')
: Adds the-Xdoclint:none
and-quiet
options to the Javadoc tasks.
Complete Example
Assuming your project needs to add these options to all Javadoc tasks, you can add the following code to your build.gradle
file:
plugins {
id 'java'
}
tasks.withType(Javadoc).configureEach {
options.addStringOption('Xdoclint:none', '-quiet')
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'junit:junit:4.13.2'
}
Effect
When you run the ./gradlew javadoc
task, the Javadoc tool will generate the documentation without performing lint checks and with minimal output. The generated Javadoc might contain improperly formatted comments, but the build process will not fail or generate numerous warnings.
Configuring Plugin Validation Behavior in Gradle
This code configures two options for all tasks of type ValidatePlugins
: failOnWarning
and enableStricterValidation
. Specifically, it sets the task to fail if any warnings occur during plugin validation and enables stricter validation rules. Here is a detailed explanation:
Code Explanation
tasks.withType(ValidatePlugins.class).configureEach {
failOnWarning = true
enableStricterValidation = true
}
Key Points
- Iterate Over ValidatePlugins Tasks:
tasks.withType(ValidatePlugins.class).configureEach
iterates over all tasks of typeValidatePlugins
, configuring each one. - Configure Validation Options: Sets the
failOnWarning
andenableStricterValidation
options to control the behavior during plugin validation.
Options Explanation
failOnWarning = true
: Setting this option totrue
means that if any warnings occur during plugin validation, the task will fail. This helps ensure that the plugin meets higher quality standards and that all potential issues are resolved before release.enableStricterValidation = true
: Enables stricter validation rules, meaning the plugin will undergo more rigorous checks during validation to ensure its definition and configuration meet best practices and the latest standards.
Use Cases
This code is particularly useful in the following scenarios:
- Improving Plugin Quality: Ensures that the plugin meets high-quality standards by failing the task if any warnings occur and enabling stricter validation rules.
- Identifying Potential Issues: Helps identify and resolve configuration issues during the development process, avoiding problems after release.
- Following Best Practices: Ensures that the plugin's definition and configuration adhere to Gradle's latest standards and best practices.
Explanation
tasks.withType(ValidatePlugins.class)
: Gets all tasks of typeValidatePlugins
.configureEach
: Configures each task.failOnWarning = true
: Fails the task if any warnings occur.enableStricterValidation = true
: Enables stricter validation rules.
Complete Example
Assuming your project needs to add these options to all ValidatePlugins
tasks, you can add the following code to your build.gradle
file:
plugins {
id 'java-gradle-plugin'
id 'com.gradle.plugin-publish' version '1.2.1'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
gradlePlugin {
plugins {
simplePlugin {
id = 'com.example.simple-plugin'
implementationClass = 'com.example.SimplePlugin'
}
}
}
tasks.withType(ValidatePlugins.class).configureEach {
failOnWarning = true
enableStricterValidation = true
}
repositories {
mavenCentral()
}
dependencies {
testImplementation 'junit:junit:4.13.2'
}
pluginBundle {
website = 'https://github.com/example/simple-plugin'
vcsUrl = 'https://github.com/example/simple-plugin'
description = 'A simple Gradle plugin'
tags = ['gradle', 'plugin', 'example']
}
publishing {
publications {
pluginMaven(MavenPublication) {
from components.java
}
}
}
Effect
When you run the ./gradlew validatePlugins
task, Gradle will validate your plugin configuration. If any warnings occur during validation, the task will fail. Additionally, stricter validation rules will ensure that your plugin configuration adheres to the latest standards and best practices.
Registering Tasks in Gradle
Detailed Explanation of TaskProvider<JooqGenerate> jooq = project.getTasks().register(taskName, JooqGenerate.class, config, jooqGeneratorRuntimeConfiguration, project.getExtensions());
This code registers a new JooqGenerate
task in Gradle. JooqGenerate
is a custom task class used to generate jOOQ
source code. Here is a detailed explanation of this code:
Code Breakdown
-
project.getTasks().register
:project
: The current Gradle project object.getTasks()
: Gets the task container for the current project.register
: Registers a new task.
-
taskName
:- The dynamically generated task name, such as
"generateMainJooq"
or"generateTestJooq"
, depending on the configuration name.
- The dynamically generated task name, such as
-
JooqGenerate.class
:- The task type is
JooqGenerate
, meaning the registered task will be of typeJooqGenerate
.
- The task type is
-
config
:- A
JooqConfig
instance containingjOOQ
configuration information. This configuration will be passed to theJooqGenerate
task instance.
- A
-
jooqGeneratorRuntimeConfiguration
:
- The runtime configuration for the
jOOQ
generator, typically aFileCollection
containing the classpath needed to generatejOOQ
source code.
project.getExtensions()
:- The extension container for the current project, used to get and configure project extensions.
Detailed Explanation
TaskProvider<JooqGenerate> jooq
registers a new JooqGenerate
task using the project.getTasks().register
method and binds it to a TaskProvider
. TaskProvider
is a way to configure and execute tasks lazily, ensuring that tasks are only configured and executed when actually needed.
Constructor of JooqGenerate
Class
In the JooqGenerate
class, the constructor is defined as follows:
@Inject
public JooqGenerate(JooqConfig config, FileCollection runtimeClasspath, ExtensionContainer extensions, ObjectFactory objects, ProviderFactory providers, ProjectLayout projectLayout, ExecOperations execOperations, FileSystemOperations fileSystemOperations) {
// Initialize properties
}
Parameter Explanation
config
: AJooqConfig
instance containingjOOQ
configuration information.runtimeClasspath
: The runtime classpath, of typeFileCollection
.extensions
: The extension container, of typeExtensionContainer
.objects
: The object factory, of typeObjectFactory
, used to create Gradle-provided objects (such asProperty
andProvider
).providers
: The provider factory, of typeProviderFactory
, used to create and manipulateProvider
instances.projectLayout
: The project layout, of typeProjectLayout
, containing information about the project directory and file layout.execOperations
: The execution operations, of typeExecOperations
, used to execute external processes.fileSystemOperations
: The file system operations, of typeFileSystemOperations
, used to manipulate the file system (such as deleting directories).
Functionality
-
Register Task:
- Registers a new
JooqGenerate
task in the Gradle project using theproject.getTasks().register
method.
- Registers a new
-
Pass Task Parameters:
- Passes the
config
,runtimeClasspath
, andextensions
parameters to theJooqGenerate
task constructor.
- Passes the
-
Lazy Configuration:
TaskProvider
allows lazy configuration of the task, ensuring that tasks are only configured and executed when actually needed, improving the performance and efficiency of the Gradle build script.
Practical Task Configuration Example
Assuming you have a build.gradle
file configured as follows:
plugins {
id 'nu.studer.jooq' version '5.2'
}
jooq {
version = '3.14.0'
edition = 'OSS'
configurations {
main { // Corresponding configuration name
generateSchemaSourceOnCompilation = true
jdbc {
driver = 'org.h2.Driver'
url = 'jdbc:h2:~/test'
user = 'sa'
password = ''
}
generator {
name = 'org.jooq.codegen.DefaultGenerator'
database {
name = 'org.jooq.meta.h2.H2Database'
inputSchema = 'PUBLIC'
}
target {
packageName = 'org.jooq.example'
directory = 'src/generated/jooq'
}
}
}
}
}
In this example, the JooqGenerate
task will generate jOOQ
source code based on the jooq
configuration and place the generated source code in the src/generated/jooq
directory.
Summary
This code registers a new JooqGenerate
task using TaskProvider
and passes the necessary configuration information to the task instance, allowing it to generate jOOQ
source code. TaskProvider
enables lazy configuration of the task, improving the performance and efficiency of the Gradle build script.
Gradle NamedDomainObjectContainer
To implement a custom Gradle plugin that allows defining a DSL similar to code { configurations { main { ... } } }
in the build.gradle
file, you need to follow these steps:
1. Create Extension Class
First, create an extension class to define the configuration items in the DSL.
CodeConfig.java
package com.example;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.model.ObjectFactory;
import javax.inject.Inject;
public class CodeConfig {
private final NamedDomainObjectContainer<Configuration> configurations;
@Inject
public CodeConfig(ObjectFactory objects) {
this.configurations = objects.domainObjectContainer(Configuration.class);
}
public NamedDomainObjectContainer<Configuration> getConfigurations() {
return configurations;
}
public static class Configuration {
private final Property<String> a;
private final Property<String> b;
@Inject
public Configuration(ObjectFactory objects) {
this.a = objects.property(String.class);
this.b = objects.property(String.class);
}
public Property<String> getA() {
return a;
}
public Property<String> getB() {
return b;
}
}
}
2. Create Plugin Class
Next, create a plugin class to register and configure the extension.
CodePlugin.java
package com.example;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class CodePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Register the 'code' extension
CodeConfig extension = project.getExtensions().create("code", CodeConfig.class, project.getObjects());
// Configure tasks or other behavior based on the extension
project.afterEvaluate(proj -> {
extension.getConfigurations().forEach(config -> {
// Example: Print configuration values
System.out.println("Configuration: " + config.getA().get() + ", " + config.getB().get());
// You can create tasks or apply configurations based on these values
});
});
}
}
3. Apply Plugin and Configure DSL
Apply the plugin and configure the DSL in the build.gradle
file.
build.gradle
plugins {
id 'com.example.code-plugin'
}
code {
configurations {
main {
a = '1'
b = 'abcd'
}
// You can define more configurations here
test {
a = '2'
b = 'efgh'
}
}
}
4. Include Plugin Path in settings.gradle
Ensure to include the plugin path in settings.gradle
for local development.
settings.gradle
includeBuild 'path/to/your/plugin'
Summary
By following the steps above, you created a custom Gradle plugin that allows defining a DSL similar to code { configurations { main { ... } } }
in the build.gradle
file. This plugin supports multiple configuration items and allows accessing and using these configuration items after evaluating the build.gradle
file.
NamedDomainObjectContainer
is a Gradle container used to manage a group of objects with unique names. It extends NamedDomainObjectCollection
and adds functionality that makes it very suitable for DSL configuration. For example, NamedDomainObjectContainer
is often used in plugin development to provide DSL blocks like configurations { ... }
or sourceSets { ... }
.
Key Features
- Named Object Management: Each object has a unique name, making it easy to access and manage these objects by name.
- DSL Support: Provides a natural way to configure objects through closures or configuration blocks.
- Automatic Creation: Automatically creates and configures new objects when referenced by name.
Example Explanation
Suppose we are developing a custom plugin that includes a NamedDomainObjectContainer
to manage a group of objects named Configuration
.
1. Define Configuration
Class
First, define a simple Configuration
class with some configurable properties.
package com.example;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import javax.inject.Inject;
public class Configuration {
private final Property<String> a;
private final Property<String> b;
@Inject
public Configuration(ObjectFactory objects) {
this.a = objects.property(String.class);
this.b = objects.property(String.class);
}
public Property<String> getA() {
return a;
}
public Property<String> getB() {
return b;
}
}
2. Create CodeConfig
Extension Class
Next, create an extension class CodeConfig
that contains a NamedDomainObjectContainer<Configuration>
to manage Configuration
objects.
package com.example;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.model.ObjectFactory;
import javax.inject.Inject;
public class CodeConfig {
private final NamedDomainObjectContainer<Configuration> configurations;
@Inject
public CodeConfig(ObjectFactory objects) {
this.configurations = objects.domainObjectContainer(Configuration.class);
}
public NamedDomainObjectContainer<Configuration> getConfigurations() {
return configurations;
}
}
3. Create Plugin Class
Next, create a plugin class CodePlugin
to register the CodeConfig
extension and process the configuration after the project is evaluated.
package com.example;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class CodePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
// Register the 'code' extension
CodeConfig extension = project.getExtensions().create("code", CodeConfig.class,
project.getObjects());
// Configure tasks or other behavior based on the extension
project.afterEvaluate(proj -> {
extension.getConfigurations().forEach(config -> {
// Example: Print configuration values
System.out.println("Configuration: " + config.getA().get() + ", " + config.getB().get());
// You can create tasks or apply configurations based on these values
});
});
}
}
4. Configure Plugin
Finally, apply the plugin and configure the configurations
in the build.gradle
file.
plugins {
id 'com.example.code-plugin'
}
code {
configurations {
main {
a = '1'
b = 'abcd'
}
test {
a = '2'
b = 'efgh'
}
}
}
How It Works
- Object Creation and Configuration: When
main
andtest
are referenced in theconfigurations
block,NamedDomainObjectContainer
automatically creates theseConfiguration
objects. - DSL Support: Configures the properties of these objects using closure blocks, e.g.,
a
andb
. - Lazy Evaluation: After the project is evaluated, the plugin can iterate over the
configurations
container and process all defined configurations.
Summary
NamedDomainObjectContainer
is a powerful tool in Gradle for managing a group of named objects, especially suitable for developing plugins that require DSL configuration. It provides a natural way to create and configure objects through closures or configuration blocks, making plugin configuration more intuitive and flexible.