GSoC 2025 - Improving Configuration Cache in Key Gradle Plugins#
This project aims to improve Gradle’s build performance by enhancing Configuration Cache support in widely-used plugins.The project will help analyze cache behavior and identify areas where multiple processes compete for shared resources, which can slow down builds.
The long-term goal is to improve developer productivity and CI reliability by making Gradle builds faster, more predictable, and easier to optimize—especially in large-scale projects.
Project Status#
- Status: Completed
- Milestone: Midterm Evaluation Successfully Passed , waiting for the Final Evaluation
- Current Focus: The project is now complete. The final phase involved extending Configuration Cache support to a wider range of community plugins, successfully scaling the architecture and insights gained from the initial work. Furthermore, the process provided invaluable learnings and contributed to a substantial increase in technical expertise.
Team#
Contributor#
Mentors#
- Rodrigo Oliveira
- Rafael Chaves
- Oleg Nenashev
Organization#
- Kotlin Foundation
Project Goals#
- Survey & Identify: To survey a list of popular Gradle community plugins and identify common patterns of Configuration Cache incompatibility.
- Fix & Contribute: To develop and submit patches for the identified incompatibilities, collaborating with plugin maintainers to get them merged.
- Document & Share: To document the process and share key learnings with the wider Gradle community to facilitate future compatibility work.
Final Project Deliverables#
This section lists all the deliverables that were successfully completed for the project.
### 1. Nebula Gradle Lint Plugin (gradle-lint-plugin
)
- Problem: The plugin's primary violation of Configuration Cache principles was its direct reference to the Project object and its use of project.configurations to resolve dependencies at configuration time. The direct Project reference has been successfully removed. However, refactoring the usage of project.configurations is still outstanding, as this part of the work requires a more significant redesign of the plugin's logic.
- Approach to Fix: The core approach was to decouple tasks and services from Gradle's live, non-serializable Project object during the task's execution phase. This was achieved through a consistent, two-pattern strategy:
Operate on Data Snapshots: The primary fix was to create simple, serializable data containers (ProjectInfo and ProjectTree). These classes act as a "snapshot" of the necessary data, which is safely extracted from the Project object during the configuration phase using Gradle's lazy Provider API. Services like LintService were refactored to be stateless, receiving these data snapshots as input instead of the Project object itself.
Defer Access with a Supplier: For rare cases where a rule absolutely required the live Project model at execution time, the fix was to inject a Supplier
. This lightweight, serializable object provides a "recipe" to get the Project object on-demand, isolating the non-cache-friendly logic and allowing the rest of the process to be cached. This strategy was applied consistently across all classes. The future work plan is to continue this pattern by creating new data projections for Project.configurations and refactoring the remaining rules incrementally. - Technical Write-up: A detailed blog post explaining the refactoring process for this plugin was published here: Supporting Configuration Cache - my learnings from the Nebula Lint Plugin.
- Pull Request: #433
-
Status: ⏳ Open
2. Gradle Play Framework Plugin (play-framework-plugin
)#
- Problem: The plugin had multiple Configuration Cache violations. The
PlayDistributionPlugin
resolved dependencies eagerly and held direct references to the non-serializableConfiguration
object. Separately, theJavaScriptMinify
task violated caching rules by callinggetProject()
during its execution phase to access file operations.
- Approach to Fix: A two-part approach was used to address the different issues:
1. For
PlayDistributionPlugin
: The fix was to create a lazy data pipeline using Gradle'sProvider
API. A new serializable data container,ComponentIdAndFile
, was introduced to safely hold artifact information. This allowed the plugin to pass dependency information without resolving theConfiguration
object during the configuration phase. 2. ForJavaScriptMinify
Task: The solution was to use dependency injection. The requiredFileSystemOperations
service was injected directly into the task's constructor, eliminating the need to callgetProject()
at execution time.
- Pull Request: #207
- Status: ⏳ Open
- Note: This pull request is not merged as Gradle team has decided to decommission this plugin in favor of a newer, official plugin within the Play Framework that is already Configuration Cache-compatible.
3. Liquibase Gradle Plugin (liquibase-gradle-plugin
)#
- Problem: The plugin was incompatible with the Configuration Cache because its main task,
LiquibaseTask
, and a helper class,ArgumentBuilder
, directly accessed the non-serializableProject
object during the execution phase. This was done to read extension properties, resolve classpaths, and get project directories and loggers.
- Approach to Fix: The solution was to make the task's execution logic stateless by extracting all necessary data during the configuration phase into serializable Data Transfer Objects (DTOs).
* Two new data container classes,
LiquibaseInfo
andProjectInfo
, were created to hold all the required data from the project and itsliquibase
extension. * These DTOs are populated safely at configuration time and passed into theLiquibaseTask
as lazy@Input
properties. * TheArgumentBuilder
class was refactored to be stateless, receiving theLiquibaseInfo
DTO as a method parameter instead of holding a reference to theProject
.
- Pull Request: #176
- Status: ⏳ Open (In communication with the maintainer and answering their questions)
4. Compose Multiplatform Localize (compose-multiplatform-localize
)#
- Problem: The plugin's main task contained multiple Configuration Cache violations by accessing the
Project
object during the execution phase. Specifically, it usedproject.projectDir
to resolve file paths andproject.logger
for logging messages.
- Approach to Fix: A two-part fix was implemented to decouple the task from the
Project
model: 1. Theproject.projectDir
access was resolved by adding a new lazy@Input
property to the task. This property is populated safely during the configuration phase and then read by the task at execution time. 2. Theproject.logger
calls were replaced by using the task's own built-in logger (getLogger()
), which is the cache-compatible way to handle logging within a task.
5. OpenRewrite Gradle Plugin (rewrite-gradle-plugin
)#
- Problem: The plugin was fundamentally incompatible with the Configuration Cache. Its core classes—
RewriteExtension
,AbstractRewriteTask
, and the parsers—were deeply coupled with the liveProject
model, directly accessing project state and holding non-serializableProject
references. - Approach to Fix: The refactoring is a significant, work-in-progress architectural change focused on completely decoupling the plugin from the
Project
model. The strategy involved several key steps: 1. TheRewriteExtension
was made serializable by extracting and storing only the necessary primitive data (like the project directory and properties) during its construction. 2. A comprehensive, serializable Data Transfer Object (DTO),ProjectInfo
, was created to act as a safe snapshot of all necessary project data. 3. TheAbstractRewriteTask
base class was refactored to consume thisProjectInfo
DTO as its primary input. 4. TheDelegatingProjectParser
was updated to be instantiated with theProjectInfo
DTO, using reflection to transfer the DTO's data across an isolated classloader boundary. - Validation: A dedicated functional test (
ConfigurationCacheTest
) was added to prove the fix. This test runs a build twice with the configuration cache enabled, verifying that no compatibility problems are found and that the cache is successfully reused on the second run. - Pull Request: Draft PR #1
- Status: 🚧 Work in Progress. This is a large and complex refactoring effort. The foundational work of introducing a DTO and decoupling the core components is underway, but more work is needed for full compatibility.
Surveyed Plugins#
This section details additional plugins that were investigated for Configuration Cache compatibility, but for which a contribution was not submitted.
6. Flyway Gradle Plugin (flyway-gradle-plugin
)#
- Problem: The plugin's abstract base task,
AbstractFlywayTask
, is fundamentally incompatible with the Configuration Cache. Its@TaskAction
logic directly queries the liveProject
model at execution time to access the buildscript classloader, project extensions, and source set outputs. This problematic logic can be seen in therunTask
andaddClassesAndResourcesDirs
methods in the source code. - Proposed Approach to Fix: The general approach would involve refactoring the Flyway tasks to be stateless. This would require capturing all necessary configuration (like source set outputs and dependent classpaths) at configuration time into serializable data objects (DTOs) and passing them to the tasks as lazy Providers.
- Reason for Not Proceeding: A contribution was not pursued because the maintainers decided to close the original feature request due to a lack of community interest. As stated in the official response in GitHub Issue #3550, This stance remains the official position despite newer requests like GitHub Issue #4107.
- Status: 🛑 Blocked
7. jsonschema2dataclass Gradle Plugin (js2d-gradle
)#
- Problem: The plugin's generation task,
Js2pGenerationTask
, was incompatible with the Configuration Cache. When storing the task's state, Gradle produced an error indicating a type mismatch where a resolvedFileCollection
was being assigned to a property expecting a lazyNamedDomainObjectProvider
. - Outcome: Following the filed issue, the maintainer corrected the incompatibility in the plugin's codebase. The fix is now present on the main branch but has not yet been part of an official release. A follow-up comment was added to the issue requesting that a new version be published.
- GitHub Issue: #884
-
Status: ✅ Fixed (Awaiting Release)
8. forbidden-apis Gradle Plugin#
- Problem: When the Configuration Cache is enabled, properties configured via the top-level
forbiddenApis
extension are not correctly applied to the tasks. This causes the task properties to be cached with empty values, leading to an error at execution time because required inputs are missing. - Proposed Approach to Fix: The fix would require refactoring the plugin to use Gradle's modern lazy
Property
APIs. The properties in theforbiddenApis
extension should be correctly wired to the corresponding properties on theCheckForbiddenApis
task so that their values are resolved before the task state is cached. - Reason for Not Proceeding: A contribution was not pursued because the plugin maintainer has clearly stated that the plugin is no longer actively maintained and that adding Configuration Cache support is not a priority.
- GitHub Issue: #269
- Status: 🛑 Blocked
Collaboration and Learning#
This project was a comprehensive endeavor in improving the Gradle ecosystem. The work involved a mix of surveying popular community plugins to identify Configuration Cache incompatibilities, developing technical fixes to address them, and collaborating with plugin maintainers to have those contributions reviewed and merged. This process highlighted the importance of clear communication, persistence, and technical expertise in open-source development.
The effort to make the Gradle ecosystem fully compatible with the Configuration Cache is ongoing, and there are many opportunities for others to help. The Gradle team maintains a curated list of popular plugins that still need compatibility work. For anyone interested in continuing this effort and making a valuable contribution to the Gradle community, the following GitHub issue is the best place to start:
➡️ List of Community Plugins with Configuration Cache Issues
Post-GSoC Plans#
My involvement with the Gradle community will not end with the GSoC program, as I am committed to supporting the contributions I've made and continuing to help improve the ecosystem. This includes maintaining my merged pull requests, as well as continuing the dialogue with the maintainers of the Liquibase plugin to help get that contribution merged. Looking ahead, I will continue to explore the community-curated list of plugins for new opportunities to contribute. The GSoC program has been an incredible learning experience, and I look forward to being an active member of the Gradle open-source community for a long time to come.
References#
- My GitHub Profile: github.com/Nouran-11
- Project Blog Post: A detailed guide on making a Gradle plugin Configuration Cache compatible, based on the work done in this project: Supporting Configuration Cache - my learnings from the Nebula Lint Plugin
- Gradle's Official Documentation: The official guide on the Configuration Cache, which served as the primary technical reference for this project.
- Community Plugin Tracking Issue: The official Gradle issue for tracking Configuration Cache compatibility in popular community plugins.
- GSoC Project Page: Improving Configuration Cache in key Gradle plugins