This message was deleted.
# configuration-cache
s
This message was deleted.
āœ… 1
m
It is compatible, this callback is actually a part of the configuration phase. It isn't called when running from cache.
šŸ‘ 1
g
So if I set some property from within
whenReady
it would be cached and accessible from execution phase?
v
Property of what?
g
Either build service or just a generic property created with
ObjectFactory#property(Class)
. Just checked it with 8.1-rc-2 and it works as expected:
Copy code
val taskPaths = objects.listProperty<String>()
gradle.taskGraph.whenReady {
  taskPaths.addAll(allTasks.map { it.path })
}

tasks.register("taskPaths") {
  val taskPaths = taskPaths
  doLast {
    logger.lifecycle("tasks: ${taskPaths.get().joinToString(", ")}")
  }
}
Running something like
./gradlew clean tP --configuration-cache
twice prints expected
tasks: :clean, :taskPaths
v
Yes, as it is a property of the task and so gets serialized and deserialized accordingly, it works. I think if you would have set a property of the build service it would not have worked, because the build service is not serialized to the build cache. It would be created freshly on first need and not be set as the code in
whenRead
would not have run.
g
Ahh, got it. It seems that build service parameters are serialized to configuration cache as well. Though I got different behavior if I interact with build service at configuration time: • without CC
Build_gradle#taskPaths
are read when creating build service early (before
taskGraph#whenReady
at least) and
parameters.paths
is always empty;
paths2
(build service exposed property) contains expected values; • with CC build service parameters contain expected values set from
whenReady
(i.e. they are somehow serialized later) and
paths2
are empty as you thought. It seems that build service parameters with CC enabled are always serialized and deserialized and it happens later that without CC.
Copy code
// taskPaths from previous above

val bs = gradle.sharedServices.registerIfAbsent("testBS", TestBS::class) {
  parameters { paths.set(taskPaths) }
}

bs.get().paths2.set(taskPaths)

tasks.register("taskPaths") {
  val service = objects.property<TestBS>()
  service.set(bs)
  usesService(bs)

  doLast { service.get().list() }
}

abstract class TestBS : BuildService<TestBS.Params> {
  interface Params : BuildServiceParameters {
    val paths: ListProperty<String>
  }

  abstract val paths2: ListProperty<String>

  fun list() {
    println("from bs params: ${parameters.paths.get().joinToString(", ")}")
    println("from bs property: ${paths2.get().joinToString(", ")}")
  }
}
Copy code
# ./gradlew cle tP --no-configuration-cache
> Task :taskPaths
from bs params: 
from bs property: :clean, :taskPaths

# ./gradlew cle tP --configuration-cache
> Task :taskPaths
from bs params: :clean, :taskPaths
from bs property:
v
Ah, hm, ok. Then I probably remembered wrongly. Your discrepancy comes from your eager realization of the build service. When calling
bs.get().paths2.set(taskPaths)
you create the build service and it seems in that moment the parameter provider gets evaluated to empty list. If you avoid such eager realization it works consistently. For example like
Copy code
val bs = gradle.sharedServices.registerIfAbsent("testBS", TestBS::class) {
    parameters { paths.set(taskPaths) }
}

tasks.register("taskPaths") {
    val service = objects.property<TestBS>()
    service.set(bs)
    usesService(bs)

    doLast { service.get().list() }
}

abstract class TestBS : BuildService<TestBS.Params> {
    interface Params : BuildServiceParameters {
        val paths: ListProperty<String>
    }

    val paths2 = parameters.paths

    fun list() {
        println("from bs params: ${parameters.paths.get().joinToString(", ")}")
        println("from bs property: ${paths2.get().joinToString(", ")}")
    }
}
Both ways will be filled in both cases
g
Yeah, I get it. But still got surprised a bit. I used similar pattern (properties on a build service to propagate parameters between extensions in different projects) to avoid using `rootProject`/`subprojects`. For example, I have extension with property for a java toolchain which applies both to both root project and subprojects via different convention plugins. On the root project convention plugin sets a version and property from that extension is used a source for a property in the build service. In the subprojects on the other hand property of an extension is derived from the build service. This way I can set toolchain java version for all projects from the root project but override it in the subproject if required. Originally I just linked root project extension properties and subproject ones directly:
Copy code
abstract class MyExt {
  abstract val javaVersion: Property<Int>
}

// in root project plugin
val ext = extensions.create("my", MyExt::class.java).apply { javaVersion.convention(17) }
java { toolchain { languageVersion.convention(ext.javaVersion.map(JavaLanguageVersion::of) } }

// in subprojects
val rootExt = rootProject.the<MyExt>()
val ext = extensions.create("my", MyExt::class.java).apply { javaVersion.convention(rootExt.javaVersion) }
java { toolchain { languageVersion.convention(ext.javaVersion.map(JavaLanguageVersion::of) } }
And with build service parameters which have to be declared the same way in each of
registerIfAbsent
calls it was quite inconvenient. Maybe
ServiceRegistry#getRegistrations()#getByName(String,Class)
would be a good substitute for early service realization to propagate properties from build service parameters