Hi, I am having an issue with enabling the configu...
# community-support
g
Hi, I am having an issue with enabling the configuration cache... we are building a pathing jar for our artifacts:
Copy code
tasks.register("pathingJar", Jar) {
    dependsOn classes
    outputs.upToDateWhen { false }
    archiveFileName = pathingJarName
    doFirst {
        manifest {
            attributes "Class-Path": configurations.runtimeClasspath.files.collect { "lib/${it.name}" }.join(" ") + " lib/${novaRootJarName}"
        }
    }
}
this fails with an error:
Cannot reference a Gradle script object from a Groovy closure as these are not supported with the configuration cache.
i am assuming this is due to the fact that we try to access the runtime classpath from the
configurations
- which is a project property... i am sure i am missing something, but i don't see a way to get the runtime classpath, i don't see which service i could inject there which could fetch it for me in a configuration cache compatible way. also
notCompatibleWithConfigurationCache
does not seem to have any effect on the task, it still fails with the same error. and i can't pull out the
configurations.runtimeClasspath.files
to the configuration phase, because then i will get an error that we are modifying the classpath after it was resolved... can someone please help me to resolve it?
okay, worked it out, this works:
Copy code
tasks.register("pathingJar", Jar) {
    dependsOn classes
    def classPath = configurations.runtimeClasspath
    outputs.upToDateWhen { false }
    archiveFileName = pathingJarName
    doLast {
        manifest {
            attributes "Class-Path": classPath.files.collect { "lib/${it.name}" }.join(" ") + " lib/${novaRootJarName}"
        }
    }
}
if i fetch the classpath during configuration, but don't resolve it to files, it works fine.
v
It might not fail, but i will not work either. Besides the various bad practices you use in that small snippet, one of them is changing the configuration of a task at its execution phase. And additionally you do it in a
doLast
action where it is absolutely pointless as it does not change the result anymore in any way.
That the
notCompatibleWithConfigurationCache()
did not help is actually pretty clear. It has mainly the same effect as downgrading the errors to warnings which also does not guarantee that the behavior is as expected then and most often is not. The CC entry is still built and used, it is just not persisted and any errors degraded to warnings.
You probably want something like just (although in Kotlin DSL)
Copy code
tasks.register("pathingJar", Jar::class.java) {
    archiveFileName = pathingJarName
    manifest {
        attributes("Class-Path" to configurations.runtimeClasspath.zip(tasks.jar.flatMap { it.archiveFile }.map { it.asFile }) { cp, jar ->
            (cp + jar).joinToString(" ") { "lib/${it.name}" }
        })
    }
}
g
yeah, it was definitely not doing what i wanted - it was originally
doFirst
instead, i swapped it to
doLast
while i was struggling with the configuration cache, it does what i need it to do with
doFirst
:) looking at the solution you proposed: wouldn't this just cause the runtimeClasspath to be evaluated during the configuration phase - meaning i would have errors when the classpath is modified later?
v
You should never change the configuration of a task at execution time except for very rare edge cases. That for example also disturbs up-to-date checks which is probably also why you disabled up-to-dateness (in a bad way,
doNotTrackState()
would be better than
outputs.upToDateWhen { false }
but actually both are unnecessary if you configure properly).
wouldn't this just cause the runtimeClasspath to be evaluated during the configuration phase
Sure, which is also fine. Why should you not do it at configuration time? In the version I showed it uses a
Provider
for the value. So when using configuration cache the value is calculated right before the CC entry is stored and so all configuration should be finished. Also the
depensdOn
does not make much sense. 1. any explicit
dependsOn
that does not have a lifecycle task on the left-hand side is a code smell that usually is a hint that something is not set up properly, e.g. task outputs are not properly wired to task inputs which brings in necessary task dependencies automatically. 2. why should you need to depend on
classes
to build that jar. You only need the file names of the jars and for that no compiling or jaring or whatever is necessary.
g
> You should never change the configuration of a task at execution time except for very rare edge cases. we have an
Artifact Transform
(https://docs.gradle.org/current/userguide/artifact_transforms.html) set up. if I resolve the runtime classpath in the configuration phase, i will get this error:
Copy code
> Failed to notify project evaluation listener.
   > Cannot change dependencies of dependency configuration ':api' after it has been included in dependency resolution.
   > Cannot change attributes of configuration ':api' after it has been locked for mutation
v
My version does not have that problem, because it is in Kotlin DSL,
configurations.runtimeClasspath
is a
Provider
that I then
zip
with another
Provider
. In Groovy DSL you would use
configurations.named('runtimeClasspath')
for example.
Or you switch to Kotlin DSL. By now it is the default DSL, you immediately get type-safe build scripts, actually helpful error messages if you mess up the syntax, and amazingly better IDE support when using a good IDE like IntelliJ IDEA or Android Studio.
g
okay, so in groovy it should look something like:
Copy code
tasks.register("pathingJar", Jar) {
    archiveFileName = pathingJarName
    manifest {
        attributes "Class-Path": configurations.named("runtimeClasspath").map { it.files.collect { "lib/${it.name}" }.join(" ") + " lib/${novaRootJarName}" }
    }
}
👌 1
gotcha, thank you!