I register a custom task in root-level build.gradl...
# gradle
s
I register a custom task in root-level build.gradle.kts:
Copy code
tasks.register<ExampleTask>("example")

abstract class ExampleTask : DefaultTask() {
    @TaskAction
    fun initialize() {
        println("Hello World")
    }
}
But when I run it
./gradlew example
I got:
Copy code
Could not create task :example
    Could not create task of type ExampleTask
        Class Build_gradle.ExampleTask is a non-static inner class.
What do I miss? Thanks
And I choose not to put it in buildSrc since it re-invalidates all modules for every single change
m
That seems to work for me. What's your Gradle version?
e
are you sure that's all that's in your
class ExampleTask
? it must not use anything in the script context, e.g.
Copy code
val x = file(".")
abstract class ExampleTask : DefaultTask() {
    init {
        x // bad
        file(".") // also bad
☝️ 1
🎯 2
thank you color 1
s
@mbonnin I'm using 8.1 @ephemient Yes, the println is all I have inside. Weird is if I put it in buildSrc, everything is fine.
Oh you're right. My bad, I have a field which has access to
rootProject
. Removing it now works fine. But alas, I will lost all the API I need for the task logic if I choose this option (put the class task inside build script directly) :(
e
you do have access to
Task.project
inside the task class, so
project.rootProject
is OK, just
rootProject
is not. however using
project
at execution time is not allowed with configuration cache, what are you trying to do?
❤️ 1
s
so
project.rootProject
is OK,
Ah right. Thank you so much!
using
project
at execution time is not allowed with configuration cache,
Oh TIL. But if I don't have the cache enabled, all good?
what are you trying to do?
This task is to create a new File inside certain project directory. Let's just say is for improving dev productivity or so..
e
you can separate out the configuration from the execution,
Copy code
tasks.register("example", ExampleTask::class) {
    outputFile.set(rootProject.file("output.txt"))
}

abstract class ExampleTask : DefaultTask() {
    @get:OutputFile
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun initialize() {
        outputFile.get().getAsFile().writeText("Hello World")
    }
}
s
I will make a note on it 👍 Again, thanks @ephemient! *Why you always know everything in mobile landscape with such accurate analysis! 😅
v
And I choose not to put it in buildSrc since it re-invalidates all modules for every single change
Don't use
buildSrc
, but one or more included builds with one or more projects each, then only things get invalidate that actually use the changed thing. 🙂
👍 1
thank you color 1
a
@Vampire isn't
buildSrc
sort of "implicitly included build"? source: https://docs.gradle.org/current/userguide/organizing_gradle_projects.html In that case, why would it be worse than an explicitly included build?
v
That sentence is a bit misleading.
buildSrc
is not an included build. It is to some lengths very similar to an included build, especially since 8.0. But still, it is not an included build, just somewhat similar. It has significant differences when it comes to details and is always a bit special.
a
So do you happen to know if it is still true as of
8.+
that a pre-compiled script plugins in
buildSrc
are performing worse than an included build? I see they are marked as recommended here: https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html
(That page doesn't explicitly mention
buildSrc
though)
v
Do do you happen to know if it is still true as of
8.+
that a pre-compiled script plugins in
buildSrc
are performing worse than an included build?
To answer that you would probably need to elaborate on what exactly you mean. Especially as it sounds like you compare apples to pears. Are you talking about precompiled script plugins in
buildSrc
vs. binary plugins in included builds? Or are you talking about precompiled script plugins in
buildSrc
vs. precompiled script plugins in included builds? Or are you talking generally about precompiled script plugins vs. binary plugins? Because you can have both in both. And either way, precompiled script plugins should just be syntax sugar on top of binary plugins and the result should be the same and thus also perform the same. If we talk about
buildSrc
vs. included builds, then it heavily depends on the situation. In small- to mid-sized builds, you would probably not see much difference, especially since 8.0. But there are differences. But those differences also heavily depend on the situations, for example whether you have all plugins in one project in one included build, or split into multiple project or even multiple included builds, and whether anyway all plugins and tasks are used in the majority of projects in the build, or whether some are used there, some other used somewhere else, ......
a
Thanks for a very elaborate answer. However, my question was directly related to your earlier recommendation: https://kotlinlang.slack.com/archives/C19FD9681/p1700531879855619?thread_ts=1700523922.511279&amp;cid=C19FD9681 which sounds like included builds are always to be preferred for performance reasons (because they don't get invalidated as frequently as
buildSrc
)
v
That answer was following the statement
And I choose not to put it in buildSrc since it re-invalidates all modules for every single change
So in the case here the OP is aware that this happens and obviously has big enough trouble with the performance that he cares. I personally indeed always prefer included build over
buildSrc
for various reasons, except for some rare edge cases that can only be solved using
buildSrc
. In most projects out there the performance differences should be neglectable. But it really depends on the single situation. And it really was only about
buildSrc
vs. included build, independent of what you put inside. For example
buildSrc
is always, independently of its usage, built before the main built and then put in a classloader that is a parent of the root project classloader. This has several implications. For example if you have in
buildSrc
a dependency on lib-x:v1 but apply in a build script a plugin needing lib-x:v2, the plugin will not work unless you add a dependency constraint in
buildSrc
to upgrade
lib-x
as the parent classloader wins and there is no conflict resolution. If you e.g. have multiple projects in multiple included builds, only the projects that are actually used are built, and they are added to the normal build script classpath and participate in normal conflict resolution, so lib-x:v2 would end up on that build script's classpath. On the other hand, if you need to monkey-patch a 3rd-party plugin's class, you can do that with a class in
buildSrc
exactly because of the parent classloader situation. But due to that parent classloader of root build script, the
buildSrc
classes are also part of the build environment of all build script in the project whether they use anything from
buildSrc
or not. So if anything in
buildSrc
changes, the classpath of all tasks in all projects changes so they are not up-to-date and need to rerun. .....
a
That was a super-helpful explanation @Vampire. Thank you ❤️
👌 1
376 Views