Skip to main content

2024-07-29

One-liner

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

  1. Toolchain Configuration: Specify the required Java version and vendor in the build.gradle file.
  2. Automatic Download: If the specified JDK is not found locally during the build process, the plugin will automatically download it from the Foojay service.
  3. 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 the plugins 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

  1. Iterate Over All Configurations: configurations.all iterates over all configurations in the project (e.g., compileClasspath, runtimeClasspath, etc.).
  2. Set Resolution Strategy: c.resolutionStrategy.eachDependency sets a resolution strategy for each dependency.
  3. Check Dependency: DependencyResolveDetails object contains information about the current dependency. You can get the group and name of the dependency through details.requested.group and details.requested.name.
  4. Force Specific Version: details.useVersion '3.0.1' forces the version of the jakarta.xml.bind:jakarta.xml.bind-api dependency to 3.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 is jakarta.xml.bind:jakarta.xml.bind-api.
  • details.useVersion '3.0.1': Forces the version of the matching dependency to 3.0.1.

Use Cases

This code is particularly useful in the following scenarios:

  1. 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.
  2. Ensuring Version Consistency: Ensures that all configurations in the project use the same version of jakarta.xml.bind-api, improving consistency.
  3. 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

  1. Iterate Over Compile Tasks: tasks.withType(AbstractCompile).configureEach iterates over all tasks of type AbstractCompile (e.g., JavaCompile, GroovyCompile, etc.), configuring each one.
  2. 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

  1. -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.
  2. -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:

  1. Improving Code Quality: By treating warnings as errors, it ensures that code compiles without any warnings, helping to maintain high code quality.
  2. Strict Code Review: In team development, forcing the resolution of all warnings helps team members write more standardized and safer code.
  3. 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 type AbstractCompile.
  • 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

  1. 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, and value is the content of the custom value.

  2. identityPath.path + "#jvmVersion" and identityPath.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.
  1. testJavaRuntimeVersion and testGradleVersion:
    • 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:

  1. Debugging and Troubleshooting: When a build fails or encounters issues, knowing the JVM and Gradle versions used can help quickly pinpoint the root cause.
  2. Performance Analysis: When analyzing build performance, knowing the environment information (such as JVM and Gradle versions) can help optimize the build process.
  3. 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

  1. Iterate Over Javadoc Tasks: tasks.withType(Javadoc).configureEach iterates over all tasks of type Javadoc, configuring each one.
  2. Configure Javadoc Options: Adds specific options to the Javadoc tasks using the options.addStringOption method.

Javadoc Options Explanation

  1. -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.
  2. -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:

  1. Quickly Generating Javadoc: Quickly generating Javadoc without strict checks on the comment content.
  2. Reducing Build Log Noise: Using the -quiet option to reduce output during Javadoc generation, making the build logs cleaner.
  3. 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 type Javadoc.
  • 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

  1. Iterate Over ValidatePlugins Tasks: tasks.withType(ValidatePlugins.class).configureEach iterates over all tasks of type ValidatePlugins, configuring each one.
  2. Configure Validation Options: Sets the failOnWarning and enableStricterValidation options to control the behavior during plugin validation.

Options Explanation

  1. failOnWarning = true: Setting this option to true 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.
  2. 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:

  1. Improving Plugin Quality: Ensures that the plugin meets high-quality standards by failing the task if any warnings occur and enabling stricter validation rules.
  2. Identifying Potential Issues: Helps identify and resolve configuration issues during the development process, avoiding problems after release.
  3. 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 type ValidatePlugins.
  • 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

  1. project.getTasks().register:

    • project: The current Gradle project object.
    • getTasks(): Gets the task container for the current project.
    • register: Registers a new task.
  2. taskName:

    • The dynamically generated task name, such as "generateMainJooq" or "generateTestJooq", depending on the configuration name.
  3. JooqGenerate.class:

    • The task type is JooqGenerate, meaning the registered task will be of type JooqGenerate.
  4. config:

    • A JooqConfig instance containing jOOQ configuration information. This configuration will be passed to the JooqGenerate task instance.
  5. jooqGeneratorRuntimeConfiguration

:

  • The runtime configuration for the jOOQ generator, typically a FileCollection containing the classpath needed to generate jOOQ source code.
  1. 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: A JooqConfig instance containing jOOQ configuration information.
  • runtimeClasspath: The runtime classpath, of type FileCollection.
  • extensions: The extension container, of type ExtensionContainer.
  • objects: The object factory, of type ObjectFactory, used to create Gradle-provided objects (such as Property and Provider).
  • providers: The provider factory, of type ProviderFactory, used to create and manipulate Provider instances.
  • projectLayout: The project layout, of type ProjectLayout, containing information about the project directory and file layout.
  • execOperations: The execution operations, of type ExecOperations, used to execute external processes.
  • fileSystemOperations: The file system operations, of type FileSystemOperations, used to manipulate the file system (such as deleting directories).

Functionality

  1. Register Task:

    • Registers a new JooqGenerate task in the Gradle project using the project.getTasks().register method.
  2. Pass Task Parameters:

    • Passes the config, runtimeClasspath, and extensions parameters to the JooqGenerate task constructor.
  3. 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

  1. Named Object Management: Each object has a unique name, making it easy to access and manage these objects by name.
  2. DSL Support: Provides a natural way to configure objects through closures or configuration blocks.
  3. 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 and test are referenced in the configurations block, NamedDomainObjectContainer automatically creates these Configuration objects.
  • DSL Support: Configures the properties of these objects using closure blocks, e.g., a and b.
  • 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.