Avoid afterEvaluate when configuring KSP based on an extension

Hello! I may have an XY problem here, but hopefully, I can get the idea across.

I’m currently working on a Gradle plugin that’s already been released - the KordEx plugin found in this repo. For the most part, everything works - but I’m running into what appears to be a timing issue.

Essentially, I’m trying to automatically configure the required annotation processor for my project, which is run using KSP. However, KSP does not generate any tasks if the dependency isn’t present, apparently waiting until afterEvaluate to look through the dependencies and create its tasks.

My plugin is relatively complex, and I need to be able to access the properties in its extension before I’m able to figure out and apply this dependency. This means my plugin currently uses afterEvaluate, as it appears that its logic cannot be run in a task (or at least, I assume not). I am also unable to apply the KSP plugin myself, as KSP plugin versions are dependent on the version of Kotlin you’re using, which isn’t something my plugin prescribes - and I couldn’t find a way to dynamically add a third-party plugin with an arbitrary version.

So, my question is - how can I configure my plugin “in between” the extension being configured and afterEvaluate? I suspect that this may not actually be possible, though - so I’m curious if anyone else has run into this.

Much appreciation for reading!

I managed to get hold of a Gradle wizard within my proximity, but we’re both a little stumped - I’ve rewritten the plugin to use providers, and to add the dependencies via configuration.dependencies.addAllLater - but even though I can see the dependencies are being added, the KSP plugin still isn’t generating the tasks.

Should I assume this is the earliest possible time I can do this work, and seek help from the KSP community?

Using afterEvaluate is practically never a solution, but duct tape. The main effect of afterEvaluate is to introduce timing problems, ordering problems, and race conditions as you also found out. It is like using SwingUtilities.invokeLater or Platform.runLater to “fix” a GUI problem. It just does symptom treatment, delaying the problem to a later, harder to reproduce, harder to debug, and harder to fix point in time.

You shared too little information about the concrete situation and no MCVE. But your situation sounds like one where I typically recommend not to use properties in the extension, but add a function to the extension. The things your consumers would before configure as properties, they give as arguments to the function instead, and the function body does the logic that needs to react to the parameters’ values.

1 Like

Thanks for your response!

I did push my code last night, but I forgot to link it, apologies.

The aim here is for my plugin to make project configuration (when using my framework) as easy and standard as possible - it allows the user to specify the version of my framework they wish to use, and defaults to the latest version if they don’t. The version that ultimately ends up getting used decides the package and version for the KSP annotation processor, so the plugin aims to add that dependency automatically if the KSP plugin is present. However, I cannot simply apply the KSP plugin myself, as the version of the plugin you need depends on the version of Kotlin you’re using for some reason.

It appears to me that KSP must be resolving the configurations too early, somehow earlier than the linked code block above is run - that’s where my confusion lies for the most part.

Moving to a function is an idea I’ve considered, though it would make for a slightly worse API, given that all the relevant properties have default values that can be omitted. If it’s the only solution, then I’ll still take it, though.

I have no idea what the KSP plugin does when.
Maybe it just cannot see dependencies added through addAllLater no matter when you add them?
:man_shrugging:

though it would make for a slightly worse API, given that all the relevant properties have default values that can be omitted

How worse or if at all also depends on how you do it.
You could for example also have a function that takes an Action<MyPropertyHolder>, then the user would just have one more nesting level but could still do

yourExtension {
    yourMethod {
        propertyA = 1
        propertyB = 2
    }
}

and then in the method create a MyPropertyHolder instance, give it to the supplied action and then do something with the values. If the changes you do are “fixable” if the yourMethod is called again, it might make sense to also add a check that the method is only called once.

1 Like

I suspect that may be the case, too - we dug through the code a bit and, well, yeah, they’re doing a lot of odd stuff.

I’ll give your approach a try and see how I get on - thanks for the help.

1 Like

Unbelievably, this doesn’t work either - I think I’ll have to assume this is something to take up with the KSP developers in that case.

I massively appreciate your attempts to help, though, Björn - thanks a bunch!

1 Like

Upon further investigation, it appears that my approach using configuration.dependencies.addAllLater was working, but the KSP plugin seems to configure its tasks to be hidden, so it was very difficult to tell whether things were working without trying to build a test-case with the annotations that needed processing.

I’m at least glad things worked out, even if I am annoyed that the KSP plugin’s design made figuring this out so hard!

1 Like

Well, sounds like those tasks should not be called manually, so makes sense they are hidden.
Hidden though just means that you have to use the --all parameter to tasks or look in the “other” category in the IDE tool window to find the task as hidden just means they have no category set. :slight_smile:

1 Like

That’s useful to know, thanks - though I’ll note they didn’t show in my IDE either!

Did you sync after doing the changes?
Afair there is no way to hide a task like that.
Just not setting a group so that it lands in other in the IDE and is not displayed by tasks without --all.
Or did you not actually talk about new added tasks?

Yep, that was the case after sync, even when I verified KSP was working - but I’m using K2 mode in IDEA 2024.2 which has been remarkably buggy, so I’m not chalking that up to a Gradle problem

EDIT: Just checking it again now in the gradle-plugins project after a full system reboot, it is showing finally - but I’ve got users stating they still can’t see it. Strange for sure, though at least, very much not breaking.