javagradle

How to add a "Class-Path" entry to a Jar manifest in Gradle compatible with the configuration cache?


I'm using Gradle (currently V8.9) to create JAR files with a "Class-Path" entry in the manifest that points to all JARs on the respective project's runtimeClasspath. How to do this has been answered several times, but always years ago (e.g., Add classpath in manifest using Gradle). Here's the Groovy build script snippet that I used successfully for several years now (for all Java subprojects in a multi-project build):

    jar.doFirst {
        manifest {
            if (!configurations.runtimeClasspath.empty) {
                attributes('Class-Path':configurations.runtimeClasspath*.name.join(' '))
            }
        }
    }

Now, I want to enable the "configuration cache". If I do so, Gradle (correctly) complains that I access a configuration during execution time: Cannot reference a Gradle script object from a Groovy closure as these are not supported with the configuration cache.

So I tried to move the Class-Path code to configuration time and looked at several examples from the Internet. One of several attempts is this:

    jar {
        def classpath = configurations.runtimeClasspath.files
        // Input declaration is needed for the proper up-to-date checks
        inputs.files(classpath).withNormalizer(ClasspathNormalizer)
        manifest {
            attributes('Class-Path':classpath.collect { cp -> cp.join(' ') { it.name } })
        }
    }

With all my attempts, there is an error because the configuration seems to become immutable too early now, so that other parts of the build script cannot add dependencies etc. One error message I got with these attempts is: Cannot change attributes of configuration ':SomeProject:runtimeClasspath' after it has been locked for mutation Another is: Cannot change dependencies of dependency configuration ':SomeOtherProject:api' after it has been included in dependency resolution.

This is a pretty standard use case and the configuration cache has become rather stable, recently, too. So I guess this should be possible and not too hard. So how can I add a "Class-Path" entry to a JAR manifest in a multi-project build based on the runtimeClasspath configuration and stay compatible with the configuration cache?


Solution

  • I think I found a solution:

            jar {
                def classPathProvider = configurations.runtimeClasspath
                    .incoming
                    .artifacts
                    .resolvedArtifacts
                    .map { artifacts ->
                        artifacts.collect { it.file.name }.join(' ')
                    }
                inputs.files(configurations.runtimeClasspath)
                      .withNormalizer(ClasspathNormalizer)
    
                manifest {
                    attributes('Class-Path':classPathProvider)
                }
            }
    

    Using the "incoming" field of Configuration and the respective further methods, one can get a "Provider" for the class path that can be lazyly evaluated.