I have a plugin in my buildSrc that generates kotl...
# gradle
p
I have a plugin in my buildSrc that generates kotlin code. I consume that plugin through a task in one of my modules. Naively I let the KotlinCompile task depend on that task:
Copy code
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
  dependsOn(generateKotlinCodeTask)
}
However this one now fails on multiplatform. I debugged the task names and I can make it work if I add that task dependency for
KotlinNativeCompile
as well. However I'm asking myself if this is the correct approach to hook code generation into the build process.
v
It is not and also sounds like it only works because you put the generated files into where your normal files are also. This is for example bad for up-to-date checks or cacheability and similar. I'd generate these sources to somewhere else like for example
build/generated-sources/<taskName>/kotlin
and then wire the sources in properly. With Java it would be:
Copy code
val mySourceGeneratingTask by tasks.registering(...) {
    ...
}
val generate by tasks.registering {
    dependsOn(mySourceGeneratingTask)
}
sourceSets {
    main {
        java {
            srcDir(mySourceGeneratingTask)
        }
    }
}
With Kotlin it is probably very similar, though I didn't try that yet. The
generate
task is purely optional I just like to have a lifecycle task that depends on all generation tasks. And if you care about IntelliJ integration, you would also register that source directory as generated sources in the
idea
plugin of course. 🙂
p
Currently I put them in the generated directory:
fun generatedStoriesDirectory() = File(buildDir, "generated/stories/code")
Now I add that directory to the source set:
Copy code
kotlin {
  sourceSets {
    commonMain {
      kotlin.srcDir(generatedStoriesDirectory())
Pass the directory to my task:
Copy code
val generateStoriesTask = tasks.register<GenerateStoriesTask>("generateStories") {
  csvFile.set(File(projectDir, "stories.csv"))
  output.set(generatedStoriesDirectory())
}
What I don't understand about your solution is where I would define that build directory. And how can a srcDir have a task as an input
v
Ah, ok, then you almost did it right actually. 😄 It is always better to not use
dependsOn
if you can prevent it. When using the task as src dir, it's output is used as sources. And the added benefit is, that you have an implicit task dependency so whenever the sources are needed and the task is not yet run or out-of-date, it will automatically be run. Even if you build a source jar or iterate over source files or similar.
You should avoid to pass paths around. But always configure inputs and outputs of tasks properly and then wire task output as inputs to other tasks or properties where possible
p
Huh it's working! 🙂
I changed my task output to not be configurable:
Copy code
@get:OutputDirectory
abstract val output: Property<File>
->
Copy code
@get:OutputDirectory
  val output: File = File(project.buildDir, "generated/stories/code")
And now I just register the task:
Copy code
val generateStoriesTask = tasks.register<GenerateStoriesTask>("generateStories") {
  csvFile.set(File(projectDir, "stories.csv"))
}
And use it as a srcDir:
kotlin.srcDir(generateStoriesTask)
Thanks a lot, this makes way more sense now!
v
Just some more sidenotes if you care. I wouldn't ever use
Property<File>
, but
DirectoryProperty
or
RegularFileProperty
as that then also has the right semantics for example. If you don't want it to be changeable anymore (changeable would be no problem it would still work) you could also use
disallowChanges()
like
Copy code
@get:OutputDirectory
abstract val output: DirectoryProperty

@get:Inject
abstract val layout: ProjectLayout

init {
    output
        .value(layout.buildDirectory.dir("generated/stories/code"))
        .disallowChanges()
}
or a
Provider<Directory>
like
Copy code
@get:OutputDirectory
val output: Provider<Directory>

@get:Inject
abstract val layout: ProjectLayout

init {
    output = layout.buildDirectory.dir("generated/stories/code")
}
This way you don't capture the
buildDir
that is configured at the time the task is instantiated but will follow suit if the build directory gets reconfigured later.
p
Works, thanks! Ive also just set the properties itself to internal:
Copy code
@get:InputFile
  abstract val csvFile: RegularFileProperty

  @get:Inject
  internal abstract val layout: ProjectLayout

  @get:OutputDirectory
  internal abstract val output: DirectoryProperty

  init {
    @Suppress("LeakingThis")
    output.value(layout.buildDirectory.dir("generated/stories"))
      .disallowChanges()
  }
v
May I ask why?
For the
layout
yeah, or maybe even
protected
, but why for the
output
? There are indeed situations where it is better or necessary to use the property or provider directly. Just where you can use the task directly it is more concise to do so
p
@Mario Noll cc
Ah yes, protected is better. Well I want to hide information that does not need to be accessible to my plugin consumers.
v
Yeah, sure, totally clear about the
layout
but imho output properties should be accessible for further wiring when wiring the task itself is not appropriate. 🙂
p
It's an internal plugin, I open things on demand and be as restrictive as possible
👌 1