Ivan CLOVIS Canet
06/26/2025, 7:10 AMRegularFileProperty
which is generated by another task (in a plugin I don't control) but isn't an output.
Without configuration cache, everything works well.
With configuration cache, Gradle tries to configure the input before running any tasks, and that breaks because it's not possible to know what the file will be.
Is there a way to set a Property
with a flag that it must be lazily-generated?Ivan CLOVIS Canet
06/26/2025, 7:24 AMMartin
06/26/2025, 8:40 AMMartin
06/26/2025, 8:41 AMIvan CLOVIS Canet
06/26/2025, 8:41 AMIvan CLOVIS Canet
06/26/2025, 8:41 AMSo probably an issue with the other plugin?Yeah but it's the KGP so…
Martin
06/26/2025, 8:42 AMdon't know what the path is going to be until the other task runsThat cannot work I think? Gradle needs to know the path to put in CC for an example.
Martin
06/26/2025, 8:42 AMKGPinteresting. What property is this?
Ivan CLOVIS Canet
06/26/2025, 8:43 AMFile(compilation.npmProject.require("vite"))
allows to task KGP to scan the node_modules files to find the binary. That only works after the :kotlinNpmInstall
task has runIvan CLOVIS Canet
06/26/2025, 8:44 AMMartin
06/26/2025, 8:44 AMkotlinNpmInstall
😅 . That part of KGP feels a bit alien.Martin
06/26/2025, 8:45 AMIvan CLOVIS Canet
06/26/2025, 8:45 AMMartin
06/26/2025, 8:45 AMMartin
06/26/2025, 8:46 AMIvan CLOVIS Canet
06/26/2025, 8:46 AMMartin
06/26/2025, 8:48 AMMartin
06/26/2025, 8:48 AMIvan CLOVIS Canet
06/26/2025, 8:48 AMMartin
06/26/2025, 8:49 AMMartin
06/26/2025, 8:49 AMMartin
06/26/2025, 8:50 AMIvan CLOVIS Canet
06/26/2025, 8:50 AMMartin
06/26/2025, 8:51 AMMartin
06/26/2025, 8:51 AMVampire
06/26/2025, 10:03 AMI've used it very rarely but my understanding is that it allows to model changing "CC-inputs"
[...]
Like if an env variable changes, it won't invalidate your CCExactly the other way around. If the value source result changes, the CC entry is discarded and configuration is done freshly. They are always evaluated once to determine whether the value changed. And if the CC entry is discarded and recalculated it is computed a second time. The value of the value source cannot change within one build and I don't think it can execute a task unless you start a separate build in a new process which you most probably should not do. With CC, values of `Provider`s (and thus also `Property`s) are evaluated after configuration was done and before CC entry is serialized, serializing the value of the
Provider
and reusing that value if CC is reused.
The only exception is, if the Provider
has a task dependency in which case the value is of course only evaluated on the first actual get()
so that the task was already executed.
Without the task dependency it most probably also just appears to work properly, because you miss the task dependency, unless you added an explicit task dependency which is bad practice.
What you want is most probably something like myRegularFileProperty = tasks.theTaskProducingTheFile.map { calculateTheFileAndReturnItHere }
so that the property has the necessary task dependency and thus also is not serialized into CC entry.Ivan CLOVIS Canet
06/26/2025, 10:19 AMvitePath.set {
project.rootProject.tasks.named("kotlinNpmInstall")
.map { File(compilation.npmProject.require("vite")) }
.get()
}
but it doesn't fix the problem, the value is still evaluated before the :kotlinNpmInstall
task runs.Vampire
06/26/2025, 10:20 AMVampire
06/26/2025, 10:20 AMget()
it, so it is evaluated immediately.
You should never get()
a provider at configuration time optimally but always only at execution time.Vampire
06/26/2025, 10:21 AMset { ... }
is probably a bad ideaVampire
06/26/2025, 10:21 AMVampire
06/26/2025, 10:21 AMVampire
06/26/2025, 10:22 AMIvan CLOVIS Canet
06/26/2025, 10:22 AMAnd reaching into the root project and getting a task from there is a super bad ideaThat I do know, but I don't have a choice. That task isn't mine, and it only exists in the root project 😕
Ivan CLOVIS Canet
06/26/2025, 10:22 AMset { ... }
is probably a bad idea
What should it be? I'm in a plugin, I don't have access to the =
syntax. vitePath
is a RegularFileProperty
.Ivan CLOVIS Canet
06/26/2025, 10:36 AMvitePath.set(
project.layout.file(
project.rootProject.tasks.named("kotlinNpmInstall")
.map { File(compilation.npmProject.require("vite")) }
)
)
Martin
06/26/2025, 10:41 AMIf the value source result changes, the CC entry is discarded and configuration is done freshly.Sorry feels like I should know that but what's the benefit of using a
ValueSource
compared to "just" letting the bytecode instrumentation intercept System.getenv()
then?Vampire
06/26/2025, 11:25 AMThat I do know, but I don't have a choice. That task isn't mine, and it only exists in the root projectSure you have. That it is not your task does not mean that you cannot register an outgoing artifact using it.
I'm in a plugin, I don't have access to theYou could have, you just have to apply the according compiler plugin, either manually, or by applying thesyntax.=
koltin-dsl
plugin. (kotlin-dsl-base
would also be enough if you do not want the other effects of kotlin-dsl
, even org.gradle.kotlin.kotlin-dsl.compiler-settings
would be enough if kotlin-dsl-base
still does more than you want, the latter is the one applying the sam-with-receiver and assignment plugins).
But even without the =
, it is just an alias for calling .set(...)
, (not .set { ... }
). You call .set(...)
with a Provider<RegularFile>
, so yes, the vitePath.set(layout.file(taskProvider.map { File(...) })
should work API-wise. What error do you get?
Sorry feels like I should know that but what's the benefit of using aNone in that case, but in a value source you can do lots of things, including calling external processes or doing computation to determine the value of the value source.compared to "just" letting the bytecode instrumentation interceptValueSource
then?System.getenv()
Ivan CLOVIS Canet
06/26/2025, 12:22 PMtheThe value is evaluated before theshould work API-wise. What error do you get?vitePath.set(layout.file(taskProvider.map { File(...) })
:kotlinNpmInstall
task, so it crashes:
* What went wrong:
Configuration cache state could not be cached: field `__vitePath__` of task `:viteBuild` of type `opensavvy.gradle.vite.kotlin.tasks.KotlinViteExec`: error writing value of type 'org.gradle.api.internal.file.DefaultFilePropertyFactory$DefaultRegularFileVar'
> Cannot find node module "vite" in "/…/kotlin-vite/examples/simple/build/js/packages/example-simple"
Ivan CLOVIS Canet
06/26/2025, 12:23 PMThat it is not your task does not mean that you cannot register an outgoing artifact using it.That would require asking all of my users to apply a new plugin to their root project, which I want to avoid.
Vampire
06/26/2025, 1:40 PMwhich I want to avoidWell, do whatever you like, but I as user of that plugin would report a bug. Latest with project isolation this will be a hard error as you must not access mutable state of other projects. So latest then you have to do it that way.
The value is evaluated before theThe code should work and have the necessary task dependency implicitly. Of coursetask, so it crashes::kotlinNpmInstall
vitePath
must be declared as input property, if you do not have that yet.Vampire
06/26/2025, 1:41 PMIvan CLOVIS Canet
06/26/2025, 2:02 PMLatest with project isolation this will be a hard error as you must not access mutable state of other projects.
So latest then you have to do it that way.Can you clarify, depending on a task counts as depending on mutable state? It's not like I'm reading any of its configuration, it's purely about its existence. Also, if that's the case, KGP will be broken anyway so I'll have to wait until they redesign it 😕
Ivan CLOVIS Canet
06/26/2025, 2:02 PMOf coursemust be declared as input property, if you do not have that yet.vitePath
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val vitePath: RegularFileProperty
Ivan CLOVIS Canet
06/26/2025, 2:04 PMgit clone <https://gitlab.com/opensavvy/automation/kotlin-vite.git>
• Branch upgrades-2.2
• File vite-kotlin/src/main/kotlin/tasks/ViteExecTask.kt:50
• Reproduction command: ./gradlew -p examples/simple --include-build ../.. clean; ./gradlew -p examples/simple --include-build ../.. build --rerun-tasks --configuration-cache
Vampire
06/26/2025, 2:18 PMCan you clarify, depending on a task counts as depending on mutable state?Of course, whether the task exists or not is mutable state of the project model. It only exists if it was registered already or a plugin registering it was applied already. Immutable state would be the project path or project directory, i.e. thing that you configure in the settings script.
Also, if that's the case, KGP will be broken anyway so I'll have to wait until they redesign itWhy?
Reproduction command
compilation.npmProject
is not foundIvan CLOVIS Canet
06/26/2025, 2:19 PMAre you in the correct branch? It's in the same file, line 34is not foundcompilation.npmProject
Vampire
06/26/2025, 2:19 PMVampire
06/26/2025, 2:20 PMIvan CLOVIS Canet
06/26/2025, 2:21 PM> Also, if that's the case, KGP will be broken anyway so I'll have to wait until they redesign it
Why?When KGP is applied to a project with the JS platform, it adds a bunch of tasks in the root project to install stuff, even though KGP isn't applied to the root project. If Gradle ever becomes stricter about cross-project configuration, they will have to redesign all of that anyway, and I'll have to rewrite my plugin to work with whatever they replace that with
Ivan CLOVIS Canet
06/26/2025, 2:21 PMVampire
06/26/2025, 2:22 PMIvan CLOVIS Canet
06/26/2025, 2:22 PMVampire
06/26/2025, 2:23 PM> Task :kotlin-vite:vite-kotlin:compileKotlin FAILED
7 actionable tasks: 1 executed, 6 up-to-date
Configuration cache entry stored.
e: file:///.../kotlin-vite/vite-kotlin/src/main/kotlin/tasks/ViteExecTask.kt:58:5 Type mismatch: inferred type is Unit! but File! was expected
e: file:///.../kotlin-vite/vite-kotlin/src/main/kotlin/tasks/ViteExecTask.kt:59:11 Type mismatch: inferred type is (Task) -> Unit but (Task) -> File! was expected
e: file:///.../kotlin-vite/vite-kotlin/src/main/kotlin/tasks/ViteExecTask.kt:59:30 Unresolved reference: npmProject
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':kotlin-vite:vite-kotlin:compileKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction
> Compilation error. See log for more details
Vampire
06/26/2025, 2:24 PMIvan CLOVIS Canet
06/26/2025, 2:24 PMremove-workaround-vite-path
with the named().map
Vampire
06/26/2025, 2:26 PMIvan CLOVIS Canet
06/26/2025, 2:30 PM.buildBy
on Provider
, do you know if that exists with another name?Vampire
06/26/2025, 2:31 PMVampire
06/26/2025, 2:35 PMkotlinNpmInstall
with the upgrades-2.2
branch and then using that snippet with project.layout
adds the dependency on kotlinNpmInstall
, so that seems to be as expected.Ivan CLOVIS Canet
06/26/2025, 2:36 PMvitePath
is set?Vampire
06/26/2025, 2:39 PMIvan CLOVIS Canet
06/26/2025, 2:40 PMVampire
06/26/2025, 2:41 PMFile("""compilation.npmProject.require("vite")""")
to just verify the task dependency is there.Vampire
06/26/2025, 2:41 PMIvan CLOVIS Canet
06/26/2025, 2:42 PMVampire
06/26/2025, 2:43 PMVampire
06/26/2025, 2:43 PMIvan CLOVIS Canet
06/26/2025, 2:44 PMVampire
06/26/2025, 2:44 PMVampire
06/26/2025, 3:02 PMabstract class MyTask : DefaultTask() {
@get:Input
abstract val foo: Property<String>
}
val bar by tasks.registering
val baz by tasks.registering(MyTask::class) {
foo = bar.map {
println("Calculating foo")
""
}
}
Something I forgot on Madeira. 😕Ivan CLOVIS Canet
06/26/2025, 3:05 PMProvider.map
documentation confirms your explanationIvan CLOVIS Canet
06/26/2025, 3:06 PMVampire
06/26/2025, 3:06 PMIvan CLOVIS Canet
06/26/2025, 3:06 PMIvan CLOVIS Canet
06/26/2025, 3:34 PMvitePath.set(
project.layout.file(
project.rootProject.tasks.named("kotlinNpmInstall")
.flatMap { it.outputs.files.elements }
.map { File(compilation.npmProject.require("vite")) }
)
)
The initialization order is now correct! However now it complains that I'm using project
at execution-time 😅 But that's something I can fix for myselfIvan CLOVIS Canet
06/26/2025, 3:35 PMVampire
06/26/2025, 3:37 PMval npmProject = compilation.npmProject
vitePath.set(
project.layout.file(
project.rootProject.tasks.named("kotlinNpmInstall")
.flatMap { it.outputs.files.elements }
.map { File(npmProject.require("vite")) }
)
)
or something like thatVampire
06/26/2025, 3:38 PMIvan CLOVIS Canet
06/26/2025, 3:38 PMIvan CLOVIS Canet
06/26/2025, 3:38 PMVampire
06/26/2025, 3:46 PMIvan CLOVIS Canet
06/26/2025, 3:46 PMIvan CLOVIS Canet
06/26/2025, 3:54 PMAdam
06/27/2025, 8:31 AMAlso, if that's the case, KGP will be broken anyway so I'll have to wait until they redesign it 😕I'll be working on KGP JS soon, to make it compatible with project isolation https://youtrack.jetbrains.com/issue/KT-75899/Support-Gradle-Project-Isolation-in-KGP-JS-Wasm
Ivan CLOVIS Canet
06/27/2025, 8:35 AMAdam
06/27/2025, 8:51 AMbecause they decided to wrap NPM instead of mapping its behavior to Gradle concepts of dependencies. I guess it was much easier to do, but it's very hard to write plugins that play well with itFor some context: With KGP JS the reasoning behind the use of the root project is the node_modules dir. The JS target has a lot of default JS dependencies. Long story short: When there are lots of subprojects a node_modules dir per-subproject would use a lot of disk space, even if the subprojects has no explicit dependencies. When KGP JS was first implemented there weren't as many options as there are now to help out. I think this is really interesting topic in KMP. When integrating with another language, should KMP wrap the native tools, or should the tools be re-implemented to make them work more like Kotlin already does (i.e: make it work like JVM, since that's Kotlin's origin)? Either option has trade-offs. Making Gradle-first wrappers for the JS tools would be more maintenance and documentation. Trying to delegate to the existing tooling is quite hard when Gradle doesn't have an appropriate mechanism to support it.
Ivan CLOVIS Canet
06/27/2025, 8:52 AMIvan CLOVIS Canet
06/27/2025, 8:53 AM