Another question: I want to generate Kotlin source...
# community-support
p
Another question: I want to generate Kotlin source code and I want to configure it correctly using srcDir of a SourceDirectorySet. AFAIK, because the sourceDirectories is not a configureable collection, I need to call srcDir. The SourceDirectrySet is part of a SourceSet. But how can I use a convention for the sourceSet but also make it lazy configurable?
Ideally, named would support a Provider<String>.
Copy code
val sourceSetName = objects.property(String::class).convention(sourceSets.main.name)

sourceSets.named {
    it == sourceSetName.get()
}.configureEach {
    println("Configuring $name")
}

val s = sourceSets.register("foo")
sourceSetName.set(s.name)
prints: "Configuring main" what makes sense because the named/configureEach will be called immediately. So the only workaround I got to work is using afterEvaluate
v
I don't think
Provider
makes sense here, neither built-in nor like you showed there. Whenever you use
get()
on a provider at configuration time, you basically introduce the same problems you have when using
afterEvaluate { ... }
, getting ordering problems, timing problems, and race conditions. And the content of your
named { ... }
is of course evaluated at configuration time, because you use it with
configureEach
. This means it is checked immediately for any already existing and realized source sets and for later added ones as soon they are (if they are) realized. For the first case the property will not yet set, for the second it could be either way.
For a good recommendation, you probably need to elaborate a bit on your use-case or show some MCVE.
For example your extension could provide a function that takes the source set name as argument and then function could then do
sourceSets.named { it == theParameterOfTheFunction }.configureEach { ... }
or something like that.
Assuming the
configureEach
part is coming from your plugin and the
foo
from your consumer
p
Yeah, I notice the this problem too:
when should the provider of named be called?
This is my very basic MCVE:
Copy code
val task: Provider<Task> = provider { }

val sourceSetName = objects.property(String::class).convention(sourceSets.main.name)

sourceSets.named(sourceSetName.get()) {
    java.srcDir(task)
} 

// user
val s = sourceSets.register("foo")
// the user should somehow connect the new sourceSet with the task
sourceSetName.set(s.name)
But I use the function to configure the sourceSet immediately, I still want to support to use the main sourceSet as default/convention
v
This code is even less correct, as
named(...)
is immediately checked and only for source sets already registered at that part 😄
p
Sorry, I wanted to only to show the api...
v
There is probably not a good way to support a default for that which is race-condition safe.
Except maybe some execution time check that verifies that the user did configure something, but that also would just be a check, not a default
p
Hm. The problem afaik is to use providers in extensions that will register tasks. Because you still need to create/init the values of the provider at some time, to register the tasks. This should be handled by Gradle without using
afterEvaluate
. For DomainObjectSet, you can use
whenObjectAdded
as a workaround that basically disables laziness, but for providers there is no onChange/set callback, because the value could be computed (instead of passed as literal). Maybe Gradle could add an interface like
AutoClosable
that will evaluate the providers of an extension, at a time handled by Gradle instead using
afterEvaluate
.
m
But I use the function to configure the sourceSet immediately, I still want to support to use the main sourceSet as default/convention
Probably not the answer you're looking for but I moved the problem of connecting source sets to the caller:
Copy code
myExtension {
    // By default, generated sources are wired to the main source set
    // but the user can override that
    outputDirConnection {
      connectToKotlinSourceSet("test")
    }
}
It's more "imperative" than "declarative" but it's served us well so far.
Changing the sourceSet name is changing the task graph so it has to be somewhat declarative imperative I think?
p
Can you show your outputDirConnection function?
p
Okay, you store/pass an action around that customize the output dir of an always registered task and this dir will be created and connected by
connectToKotlinSourceSet
. Sounds like it works, but personally, I don't like it :D
👍 1
m
The task is actually created by another top level function:
Copy code
myExtension {
  service("foo") { // creates generateFooSources
    outputDirConnection {
      // overrides the default wiring of generateFooSources
      connectToKotlinSourceSet("test")
    }
  }
}
Since the user code is already controlling when the task get created, might as well control how the task is wired...
Unless you need your source set name to be generated from another task but I think this creates a configuration cycle (but not 100% sure about that)