Make task-less plugin with extension configuration-cache compatible

All configurtation cache related documentation mostly target tasks, but what if plugin does not have any tasks (or only configures other tasks).

Example situation: plugin fixes dependency scopes in maven publication (for maven-publish plugin).

Custom extension to be able to switch off optimization:

public class MyExtension {
      boolean fixDependencies
}

And the plugin (simplified for demonstration):

public class MyPlugin implements Plugin<Project> {
    @Override
    void apply(Project project) {

        // do anything only if maven-publish plugin registered
        project.plugins.withType(MavenPublishPlugin) {
              MyExtension extension = project.extensions.create('my', MyExtension)

              // act when extension values configured
              project.afterEvaluate {

                   PublishingExtension publishing = project.extensions.getByType(PublishingExtension)
                   
                   // for all registered publications
                   publishing.publications.withType(MavenPublication) { MavenPublication pub ->

                         // apply required xml changes  (and where all configuration cache problems appear)
                         pub.withXml { XmlProvider xml ->
                               // not the best example, but I need to understand how to "bypass" such values properly
                               if (!extension.fixDependencies) {
                                     return
                               }
                              
                               // configuration access
                               Configuration impl = project.configurations.implementation

                              // pseudo groovy code: changing scope for all implementation dependencies in xml
                              DependencySet deps = impl.dependencies
                              xml.asNode().dependencies[0].each { Node dep ->
                                    // for each xml dependency, search it in implemenrtation configuration (simplified)
                                    boolean isImplDep = deps.find { Dependency implDep -> implDep.group == dep.groupId.text()}
                                 .....
                              }
                         }
                   }
              }

        }
    }
}

In order to be compatible with configuration cache I can’t use neither extension object nor resolve configurations inside pub.withXml block. But how to achieve it properly?

For extensions, its recommended to use task properties - but I don’t have tasks here. Could I use a shared build service to store extension values there? If yes, please descibe how it would work a bit (what would be cached)

For configuration, if I understand correctly, I can pre-process required configuration and pass inside pom.withXml only a simple (serializable) List<MySimpleDepObject>. But this way, I will always trigger dependencies resolution (maybe even too early). Maybe there is a way to avoid it. Also, this “pre processing” will not invalidate cache when dependencies change (because cache would contain already post processed collection). Please tell me if I’m wrong

Any other advices of how such logic could be rewritten in configuration cache friendly way are also very welcome.

Thank you in advance.

The withXml is executed at execution time, which is why you basically have the same restrictions than with tasks. The mitigation strategies are quite similar, just make the necessary calculations outside and store them in a local variable, then use the value in the block.

When dependencies change, the CC would not be reused anyway. Also other dependency resolution results are part of the CC, so that should not be an issue.

But actually, you should strongly reconsider not to use withXml at all. Using withXml for anything is more like a dirty hack always and for example will cause the information in the POM be different and inconsistent with the content of Gradle Module Metadata.

A much better approach is to fix the model of you build so that the POM and GMM are generated how you expect them to be in the end.

Thank you for the answer!

How can I fix dependencies scope in the generated xml without manual (withXml) corrections? For example, implementation scope dependencies generated with runtime scope, which is wrong (api is available only with java-library but not for java or groovy plugins). Also, custom configurations like provided or optional requre manual post processing.

implementation scope dependencies generated with runtime scope, which is wrong (api is available only with java-library but not for java or groovy plugins)

If it is wrong but needs to be compile, using api is exactly the way to go. groovy anyways applies the java plugin and java-library is basically just the addition of api, so apply java-library and use api for those.

For provided use compileOnly instead.

For optional better consider using feature variants: Optional dependencies are not optional.