Hello :wave: Was wondering what is the proper way ...
# gradle
d
Hello 👋 Was wondering what is the proper way to order tasks -> i.e. I have a task that generates K sources so I finalize it with a corresponding compile task (
compileKotlin
in JVM builds and
compile${Variant}Kotlin
for 🤖 ). What is somewhat weird is that for JVM builds my task gets included when running
build
goal but on Android it is not.... If I explicitly run my task on Android project it does get finalized correctly. So just wondering what would be the cause of the difference in behavior between JVM and Android builds
I could flip around the task configuration and add explicit dependency of my generate sources task on the compile task (i.e.
compileKotlin dependsOn myTask
vs current
myTask finalizedBy compileKotlin
) but I thought that was considered a bad practice?
v
It is somewhat bad practice to do an explicit
dependsOn
if you can instead use an implicit task dependency, yes. But using a
finalizedBy
instead is just wrong and thus worse.
compileKotlin dependsOn myTask
means if
compileKotlin
needs to run, first
myTask
has to have been run before it.
myTask finalizedBy compileKotlin
means whenever
myTask
is run, run
compileKotlin
automatically after it. But the latter does not mean, that if you run
compileKotlin
, that
myTask
is run before. But either way, if you do it like that, I guess you generate the sources into the same directory where the versioned sources are present, which is another bad practice. Or you manually add the output directory of
myTask
to the source directories, which also is bad practice. Instead you should register the
myTask
as source directory, then its output will be considered source for all means like building a sources jar, requesting all sources, compilation and so on and at the same time you have the implicit task dependency for all tasks that need the sources and not only the compile task.
This is for example how you so it for Java, for Kotlin it would probably be almost the same:
Copy code
val mySourceGeneratingTask by tasks.registering(...) {
     ...
 }

 val generate by tasks.registering {
     dependsOn(mySourceGeneratingTask)
 }

 sourceSets {
     main {
         java {
             srcDir(mySourceGeneratingTask)
         }
     }
 }
The generate task is fully optional, but I like to have a lifecycle task that depends on all tasks that generate something.
so I was marking my tasks to be finalized by compile task and adding to
main.java.srcDir
based on the above explanation it sounds like configuring the src dir part should be enough
Or you manually add the output directory of 
myTask
 to the source directories, which also is bad practice.
yeah I'm adding output directory
thanks @Vampire I just changed my logic to pass task to
srcDir
and that works like a charm for JVM build (without explicit dependsOn/finalizeBy), I'll check what I need to do for Android
v
Actually using the
outputDirectory
DirectoryProperty
property should also be enough already. I didn't check, but I guess
outputDirectory
is annotated with
@OutputDirectory
, this way it also carries the implicit task dependency. So even with what you had before it should have been fine without
dependsOn
or
finalizedBy
and for Android there is hopefully also a place to add the source. But I'm not familiar with Android builds, sorry.
d
yeah looks like it works with output directory as well 👍
so it looks like there is a difference in where the
sourceSets
are stored -> on default JVM build its a project property vs android extension property
Copy code
val baseExtension = project.extensions.findByType(BaseExtension::class.java)
println("Android source sets: ${baseExtension?.sourceSets?.names}")
val androidMainSourceSet = baseExtension?.sourceSets?.findByName("main")?.java
androidMainSourceSet?.srcDir(task.outputDirectory)
println("Android main source set: $androidMainSourceSet")
Running it I see that my task source is added but it still doesn't run during the build
Copy code
Android source sets: [androidTest, androidTestDebug, androidTestRelease, debug, main, release, test, testDebug, testRelease]
Android main source set: com.android.build.gradle.internal.api.DefaultAndroidSourceDirectorySet@1b4062a2, type=com.android.build.gradle.internal.api.artifact.SourceArtifactType$JAVA_SOURCES@9aa5534, source=[src/main/java, task ':app:graphqlGenerateClient' property 'outputDirectory']
Wondering whether this is because of
kotlin-android
plugin is applied eagerly (supposedly getting fix in next release).
v
From a very quick look I'd say that is s shortcoming of the Android plugin you should report if it isn't already. https://android.googlesource.com/platform/tools/build/+/refs/heads/master/gradle/src/main/groovy/com/android/build/gradle/internal/api/DefaultAndroidSourceDirectorySet.java looks like there is only this method to query the `Object`s configured as `srcDir`s:
Copy code
@Override
@NonNull
public Set<File> getSrcDirs() {
    return fileResolver.resolveFiles(source.toArray()).getFiles();
}
So at this time the implicit task dependencies are eradicated. I guess you indeed need explicit
dependsOn
for Android and need to complain to Google about this fact.
I don't know why there isn't just a ConfigurableFileCollection on AndroidSourceDirectorySet, but this should work without needing to add task dependencies yourself
v
Ah, I was in pretty outdated sources it seems, Google is bad at finding own stuff it seems. 😄 But yeah, even in the newer code it seems just using
srcDir
is not enough. So probably what ephemient said. 🙂
d
Thanks for the info! I saw that
registerJavaGeneratingTask
but it was throwing me an exception as I'm trying to configure it from my task configuration i.e.
Copy code
val baseExtension = project.extensions.findByType(BaseExtension::class.java)!!
val androidMainSourceSet = baseExtension.sourceSets.findByName("main")?.java
androidMainSourceSet?.srcDir(task.outputDirectory)

val variants = when (androidExtension) {
    is LibraryExtension -> if (isTestSource) findTestVariants(androidExtension) else androidExtension.libraryVariants
    is AppExtension -> if (isTestSource) findTestVariants(androidExtension) else androidExtension.applicationVariants
    else -> throw RuntimeException(
        "Unsupported configuration - unable to determine appropriate compile Kotlin task. graphql-kotlin plugin only supports default JVM builds, Android libraries and applications"
    )
}
variants.forEach { variant ->
  variant.registerJavaGeneratingTask(task, task.outputDirectory.asFile.get()). // throws exception
  variant.addJavaSourceFoldersToModel(task.outputDirectory.asFile.get())
}
DefaultTaskContainer#NamedDomainObjectProvider.configure(Action) on task set cannot be executed in the current context.
was trying to add sources directly to the variant sources (e.g. debug) as well but that didn't make any difference
so my question now becomes how can I determine whether my task is going to be created or not (I create all of them lazily) -> i.e. my understanding is that if I add explicit
dependsOn
on my task that will always execute my task (even though end user might not have configured it)....
while it might be suboptimal I'm thinking an easy way out will be to forget about auto configuring android compile task dependency and just require end users to do it.....
v
If the
compile
task needs a dependency on your task, then add it. If your task is unconfigured and thus has no inputs, it will be skipped if you have annotated your properties properly.
You could maybe also make a diiiirty hack like hooking into your setters and if the setters get called, then add the dependency.
But that would be really dirty
d
If your task is unconfigured and thus has no inputs, it will be skipped if you have annotated your properties properly.
I use conventions to setup some defaults so unsure if that makes any difference
v
Why should it then not run if it if has suitable defaults?
And besides that, even if a user configures your task, it is not "going to be created" just because of that. It only gets created when it is going to be executed, except there is some violation of task avoidance.
d
👍
yeah I just tried it and it seems to work correctly
thanks for all the help!
v
yw
e
rather than
task.outputDirectoty.asFile.get()
,
Copy code
val outputDir = File(buildDir, "generated/source/...")
taskProvider.configure { outputDirectory.set(outputDir) }
variant.registerJavaGeneratingTask(taskProvider, outputDir)
wouldn't need any trickiness
v
And then maybe not
buildDir
, but the lazy
layout.buildDirectory
variant 🙂
Ah, no, doesn't matter as you also give it to
registerJavaGeneratingTask
which again does not accept lazy values 😞
seems to work