Edoardo Luppi
09/10/2025, 5:28 PMtargets {
configureEach {
tasks.named(targetName + "SourcesJar").configure {
dependsOn(generateCharsets)
}
}
}
With 2.2.20, I was getting a deprecation message that says:
> [DEPRECATION] 'targets(TargetsFromPresetExtension.() -> Unit): Unit' is deprecated. Usages of this DSL are deprecated, please migrate to top-level 'kotlin {}' extension.
After a bit of searching on my own I figured out that I just had to use:
targets.configureEach {
tasks.named(targetName + "SourcesJar").configure {
dependsOn(generateCharsets)
}
}
The deprecation definitely did not help. It seems too generic to be honest.Vampire
09/10/2025, 7:16 PMtargets { ... } is deprecated.
That's not really generic but rather specific imho.
Besides that you should most probably not do what you do there anyway.Vampire
09/10/2025, 7:19 PMdependsOn that does not have a lifecycle task on the left-hand side is a code smell and almost always a sing that you do something wrong, most often that you do not properly wire task outputs to task inputs which automatically brings the necessary task dependencies.
If you for example have a task called generateCharsets that generates sourcecode, then you should use that task itself (or a provider thereof) as srcDir, given it properly declares its outputs as it should anyway. Then all properly behaving consumers of sources, including source jars automatically have the necessary task dependency.Edoardo Luppi
09/10/2025, 7:21 PMThat's not really generic but rather specific imho.It's telling me to use
kotlin {}, which I'm already using. Would be better to show an example of targets.configureEach instead imo.Edoardo Luppi
09/10/2025, 7:23 PMOliver.O
09/10/2025, 9:35 PMAny explicitIf I'm adding a generated file to a Kotlin source set, I must also add an explicit task dependency. Am I correctly assuming that this is an exception to the above "code smell" rule?that does not have a lifecycle task on the left-hand side is a code smelldependsOn
CLOVIS
09/11/2025, 8:46 AM.buildBy to tell a directory which task creates it, but I think the first option is preferred.Oliver.O
09/11/2025, 8:49 AMVampire
09/11/2025, 8:49 AMIt's telling me to useAh I see what you mean. I don't think it can show, which I'm already using. Would be better to show an example ofkotlin {}instead imo.targets.configureEach
targets.configureEach as example as the complaint is about using targets { ... }, no matter what is inside, it cannot know that.
But I would agree that saying to use kotlin { ... } while being inside that block just one level too deep is a bit weak as suggestion @tapchicoma đ
Am I correctly assuming that this is an exception to the above "code smell" rule?No, as @CLOVIS said and I said before, you should make sure the task properly declares its outputs and then use the task itself as
srcDir or one of its output properties that is set up properly if the task has additional outputs.
That construct is not an exception, but the most typical case of what is done wrong, configuring explicit paths instead of properly wiring task outputs.Vampire
09/11/2025, 8:51 AMsrcDir while the task dependency is still there due to configuring it manually.Oliver.O
09/11/2025, 8:58 AMVampire
09/11/2025, 9:09 AMsrcDir() you - at least conceptually - add the argument to FileCollection or FileTree which preserve the task dependency if present and those are then probably wired to something like the inputs of a sources Jar task and the inputs of a compilation task.
So technically you do not wire the task outputs to inputs directly, as you are not configuring a task.
But you put the outputs to a collection that then later is used as inputs in tasks.Edoardo Luppi
09/11/2025, 9:10 AMbuild/generated/jvmMain, build/generated/nonJvmMain)?
In that case I can't directly use it as srcDir, because I need to split by KMP source set.Oliver.O
09/11/2025, 9:12 AMDunno what you mean by "a derivative of input".
class SourceSet : Input
Then it would be clear.Vampire
09/11/2025, 9:33 AMWhat if the task outputs to multiple dirsAs mentioned above, the task should optimally provide multiple output properties for those locations, and you would then use that output property as
srcDir. Output properties inherit the task dependency.
Then it would be clear.Not really.
Input is an annotation, that would be a quite strange inheritance. đ
And besides that, as I said, the source set is not an input.
It is a collection of locations that is then later used as input for a task.
Imagine it like
val outputProducingTask1
val outputProducingTask2
val outputs = listOf(
outputProducingTask1,
outputProducingTask2
)
val inputConsumingTask {
theInput.from(outputs)
}Vampire
09/11/2025, 9:35 AMEdoardo Luppi
09/11/2025, 9:35 AMthe task should optimally provide multiple output properties for those locationsAhhh got it, thanks. Yes, it does, so I was on the correct path.
Oliver.O
09/11/2025, 9:42 AM> Then it would be clear.
>
Not really.
I wanted to refer to the concept, not technicalities. Inheritance means something to brains.is an annotation, that would be a quite strange inheritance. đInput
And besides that, as I said, the source set is not an input.I get that. The thing is: Having something thatâs almost the same but not the same is where it becomes difficult to learn.
Vampire
09/11/2025, 9:56 AMOliver.O
09/11/2025, 12:33 PMEdoardo Luppi
09/11/2025, 12:35 PMAny and I'm like "well, what's the real allowed types?".Ben Liblit
09/11/2025, 12:37 PMthe source directory set is just a collection of things that happens to later be used as task inputs.Implicitly, then, the source directory set must be a collection of things that can later be used as task inputs. Not everything can, and the static types of the relevant methods donât help you know what can and what cannot. For example, we have SourceDirectorySet.srcDir(Object). The formal argument type, Object, promises more than it delivers: not every Object will work here. Does a Task work here? Does an @OutputDirectory-annotated property work here? How about a literal String? Experienced Gradlers know that all of these work, but the static type of this methodâs formal parameter doesnât make that obvious. Consider what a novice would need to do to learn what can and cannot be used as a source directory. Perhaps theyâve found the SourceDirectorySet DSL documentation, which is quite vague about what srcDir(Object) actually accepts. From there, they head to the SourceDirectorySet API documentation, and scroll to the srcDir(Object) method documentation. There they find prose explaining that âThe source directory [âŚ] is evaluated as per Project.files(Object...)â, so they go read the Project.files(Object...) method documentation. There they finally find a clear list of what they can actually use as a source directory. The âevaluated as per Project.files(Object...)â pattern applies to many Gradle APIs. Gradle experts eventually internalize most or all of the list of what works in those contexts, or at least recognize when to refer back to the corresponding documentation. But for a beginner, a loosely typed API like SourceDirectorySet.srcDir(Object) just doesnât give much guidance. I wish that the static type system helped more here, but it doesnât. I think thatâs the point that @Oliver.O was trying to make. Some of that is due to limitations of Javaâs type system, such as the lack of Haskell-style type classes. Probably some is due to backward compatibility. If one were starting from scratch, I think one could do better, even within the limitations of what Java static types can express.
Oliver.O
09/11/2025, 12:47 PMProject.files(Object...) is helpful.
While I wouldn't call myself a Gradle beginner, I actually am a casual creator of Gradle plugins with limited time I can spend on research.
So when I'm reading the docs of SourceDirectorySet.srcDir(Object srcPath) saying
the buck stops right there. A task can never be a directory, so I have to reason to be interested in internals that follow:- The source directory.srcPath
This is evaluated as perAnd yes, a properly typed API would help tremendously. But we already knew that.Project.files(Object...)
Vampire
09/11/2025, 1:16 PMthe question remains: Where did you learn of the possibility that a task (along with its outputs) can be added to a source set?I don't remember, I'm using Gradle since pre-1.0. đ But the JavaDoc tells that the argument is evaluated as per
Project.files like many similar APIs that take Object do.
But I agree that it could be explained a bit more clear, especially with the generated code use-case in the docs.
Seems to be a good candidate for a documentation improvement request. đ
But for a beginner, a loosely typed API like SourceDirectorySet.srcDir(Object) just doesnât give much guidance.Yes, that's unfortunately true. Most probably this is due to in the beginning Groovy being the only DSL and its duck-typing. Maybe not, I have no idea. In those "good old days" it was also normal to accept
Object for a task input and then later internally use Project#file or Project#files to resolve it like the built-in APIs do.
Nowadays you usually have better typed stuff like RegularFileProperty, ConfigurableFileCollection and so on.
So things are improving, but you always have to have an eye on backwards compatibility.
The fast pace with which Gradle and its APIs are still evolving is already ammunition for Gradle-dislikers to argue in favor or Maven and friends. đOliver.O
09/11/2025, 1:24 PMVampire
09/11/2025, 1:26 PMCLOVIS
09/11/2025, 2:06 PMBen Liblit
09/11/2025, 2:26 PMit's kinda a guess tbhTo upgrade that guess to certainty, follow the steps in this message, in the paragraph that starts âConsiderâŚâ. Youâll eventually end up at the Project.files(ObjectâŚ) method documentation, but the journey there may also be instructive.
CLOVIS
09/11/2025, 2:56 PMval bar by tasks.registering
tasks.register<Foo>("foo") {
f.set(bar)
}
abstract class Foo : DefaultTask() {
@get:InputFile
abstract val f: RegularFileProperty
}
but that's forbidden. I'm not sure how to make that work.Vampire
09/11/2025, 3:13 PMObject, it could not work, because which file should be wired.
But here also you have a more strictly typed API that accepts File, RegularFile, or Provider<out RegularFile>.
So as you want to wire with task dependency, you want the Provider<out RegularFile> variant.
If your task for example has a RegularFileProperty as output, you could do
abstract class Bar : DefaultTask() {
@get:InputFile
abstract val bar: RegularFileProperty
}
abstract class Foo : DefaultTask() {
@get:OutputFile
abstract val foo: RegularFileProperty
}
val foo by tasks.registering(Foo::class)
val bar by tasks.registering(Bar::class) {
bar.set(foo.flatMap { it.foo })
}
If it is a plain old task that does have a File as output file, you could still do some mapping like
abstract class Bar : DefaultTask() {
@get:InputFile
abstract val bar: RegularFileProperty
}
abstract class Foo : DefaultTask() {
@get:OutputFile
val foo: File = File("")
}
val foo by tasks.registering(Foo::class)
val bar by tasks.registering(Bar::class) {
bar.set(layout.file(foo.map { it.foo }))
}
...Edoardo Luppi
09/11/2025, 3:19 PMsrcDir.
Given that registering the task returns a TaskProvider, I'm then forced to get() it:
commonMain {
kotlin {
srcDir(myTask.get().commonDir)
}
}
This doesn't feel correct, although it might actually be.Vampire
09/11/2025, 4:00 PMVampire
09/11/2025, 4:00 PMVampire
09/11/2025, 4:00 PMsrcDir(myTask.flatMap { it.commonDir })Vampire
09/11/2025, 4:02 PMget() a Provider at configuration time you are doing something bad,
most often either breaking laziness needlessly or introducing race conditions similar to then ones you earn from using afterEvaluate.Oliver.O
09/11/2025, 4:08 PMtasks.withType(Test::class.java) {JVM.
for ((name, value) in providers.environmentVariablesPrefixedBy("TEST_").get()) {
environment(name, value)
}
}Vampire
09/11/2025, 4:18 PMJVM. đVampire
09/11/2025, 4:19 PMVampire
09/11/2025, 4:20 PMVampire
09/11/2025, 4:20 PMTEST_... env variables will invalidate the configuration cacheVampire
09/11/2025, 4:21 PMenvironment(...) would need to support `Provider`s which it does not do yet, but hopefully in Gradle 10 does if there finally the great `Property`zatin lands that was announced for Gradle 9.Oliver.O
09/11/2025, 4:22 PMVampire
09/11/2025, 4:22 PMtoString method (the old lazy mechanism some Gradle places support)Vampire
09/11/2025, 4:22 PMname is also not known, I don't think there is a way to improve that specific snippet for the time beingVampire
09/11/2025, 4:23 PMjvmArgs it would for example be different, because there you could use a jvmArgumentProviders instead. đVampire
09/11/2025, 4:24 PMEdoardo Luppi
09/11/2025, 4:24 PMI must say that flat-mapping a task provider was not even remotely in my brain.srcDir(myTask.flatMap { it.commonDir })
Ben Liblit
09/11/2025, 4:25 PMproviders.environmentVariablesPrefixedBy("TEST_") and store the returned Provider<Map<String, String>> in an @Input-annotated field; then
2. in a doFirst block, use get() to fetch the actual map and copy its contents into the test task's environment.
My intent is to postpone calling get() on that provider to as late as possible, while still tracking the task's inputs accurately for use with the configuration cache.
But given the immutability of JVM environment variables, maybe that's overkill.Vampire
09/11/2025, 4:25 PMI must say that flat-mapping a task provider was not even remotely in my brain.You can also do
srcDir(myTask.map { it.commonDir.get() }) in this case.
Here it should have the same result with the same task dependency effect.
But flatMap indeed is exactly for such use-cases.Vampire
09/11/2025, 4:28 PMFor the environment variable example, my first inclination would be:The problem is, that means you are changing the task configuration at execution time, and you should never do that. đ
Vampire
09/11/2025, 4:29 PMVampire
09/11/2025, 4:30 PMBen Liblit
09/11/2025, 4:30 PMTest.environment(Map<String, ?>) constitutes a change to the task configuration. Now that you point it out, yeah, it seems like it should. But apparently the configuration cache doesn't know that, doesn't mind, or has a different opinion.Oliver.O
09/11/2025, 4:34 PMTEST_ parameters are used to provide a test filter value which would normally be passed as an argument to --tests.Vampire
09/11/2025, 4:36 PMOliver.O
09/11/2025, 4:37 PMVampire
09/11/2025, 4:38 PMOliver.O
09/11/2025, 4:40 PM--tests arguments that would be passed to JUnit Platform.Vampire
09/11/2025, 4:41 PMTEST_... environment variables?
JUnit Platform does not.Oliver.O
09/11/2025, 4:42 PMVampire
09/11/2025, 4:44 PMOliver.O
09/11/2025, 4:48 PMmain method in tests where one could pick up command line arguments.
On JS, it doesn't have to be used as the filtering is delegated to lower-level JS framworks (KGP uses Mocha). JS/browser obviously doesn't use env vars, JS/Node could.Edoardo Luppi
09/11/2025, 4:48 PMOliver.O
09/11/2025, 4:49 PMVampire
09/11/2025, 4:50 PMjvmArgumentProvider :-)Oliver.O
09/11/2025, 4:51 PMOliver.O
09/11/2025, 4:51 PM