gmazzo/gradle-buildconfig-plugin

`generateAtSync` feature doesn't work in all cases

jasonsparc opened this issue · 4 comments

Steps to reproduce: create an empty project with java plugin applied, set up some build configs, then sync the IDE. Now open your file explorer and notice that the build folder does not have the generated sources. (Update: Strikethrough because it doesn't really reproduce the issue.)

Update: I noticed that composite builds actually reproduce the issue consistently. See my comment at #121 (comment), along with #121 (comment) (which also provides a demo repro).


Also, I believe you should be using prepareKotlinIdeaImport, and not prepareKotlinBuildScriptModel.

Quote regarding prepareKotlinIdeaImport (not prepareKotlinBuildScriptModel):

Sebastian Sellmair:

This is not documented and also has no public API. However, since I have implemented this, I can safely say: It's okay to use. 🙌

– From, https://twitter.com/Sellmair/status/1619312549140516870

I believe, both prepareKotlinIdeaImport and prepareKotlinBuildScriptModel are (sometimes) created in an afterEvaluate, which thus, won't be available when your plugin is applied.

Also, I believe prepareKotlinIdeaImport is safer, and can be created eagerly, safely!

Quote:

Alexander Likhachev:

This maybeCreate in the example breaks lazy initialization of a lazily registered task 😔

Sebastian Sellmair:

It won't force initializing any other tasks, right? Only the empty prepareKotlinIdeaImport, which is basically free?

The prepareKotlinIdeaImport task is similar to a Gradle lifecycle task: it doesn't do anything other than to exist there so that other tasks may be hooked to it. It's thus lightweight and can be safely created eagerly (unlike prepareKotlinBuildScriptModel which appears to be a heavyweight task).

Edit: Here's one reason why I believe prepareKotlinBuildScriptModel is a heavyweight task: https://youtrack.jetbrains.com/issue/IDEA-332603/The-Task-Preparekotlinbuildscriptmodel-UP-TO-DATE-step-takes-a-long-time


Suggestions:

Replace the lines at,

https://github.com/gmazzo/gradle-buildconfig-plugin/blob/v5.3.3/plugin/src/main/kotlin/com/github/gmazzo/buildconfig/BuildConfigPlugin.kt#L45-L50

…with,

try {
    tasks.maybeCreate("prepareKotlinIdeaImport")
    // The above doesn't really throw, but just in case...
} catch(_: Throwable) {
    null
}?.dependsOn(extension.generateAtSync.map {
    if (it) tasks.withType<BuildConfigTask>() else emptyList<Task>()
})

P.S. tasks.names is expensive. Please, don't use it. It creates an entire copy of the set of existing task names every time it's called. In fact, checking if a task exists via the lazy tasks.named(String) API then catching the UnknownTaskException thrown, would be way cheaper than using task.names – just imagine a large Kotlin Multiplatform repo with many native targets and a lot of subprojects, with lots of tasks: the lookup using task.names would be exponential.

So I found out that :prepareKotlinBuildScriptModel is only ever run for the root project of the root build. If your project is a composite build, then it will NOT run for the root project of each included build.

Here's an example output, from a sample extracted from a monorepo I'm currently maintaining:

Sync (console output)
Starting Gradle Daemon...
Gradle Daemon started in 4 s 560 ms
> Task :build.foundation:core:checkKotlinGradlePluginConfigurationErrors
> Task :build.foundation:core:compileKotlin UP-TO-DATE
> Task :build.foundation:core:compileJava NO-SOURCE
> Task :build.foundation:core:pluginDescriptors UP-TO-DATE
> Task :build.foundation:core:processResources UP-TO-DATE
> Task :build.foundation:core:classes UP-TO-DATE
> Task :build.foundation:core:jar UP-TO-DATE
> Task :build.foundation:foojay:checkKotlinGradlePluginConfigurationErrors
> Task :build.foundation:structure:checkKotlinGradlePluginConfigurationErrors
> Task :build.foundation:dependencies:checkKotlinGradlePluginConfigurationErrors
> Task :build.foundation:support:checkKotlinGradlePluginConfigurationErrors
> Task :build.foundation:foojay:pluginDescriptors UP-TO-DATE
> Task :build.foundation:dependencies:checksumDepsCoder UP-TO-DATE
> Task :build.foundation:structure:pluginDescriptors UP-TO-DATE
> Task :build.foundation:foojay:processResources UP-TO-DATE
> Task :build.foundation:dependencies:pluginDescriptors UP-TO-DATE
> Task :build.foundation:structure:processResources UP-TO-DATE
> Task :build.foundation:dependencies:processResources UP-TO-DATE
> Task :build.foundation:support:compileKotlin UP-TO-DATE
> Task :build.foundation:support:compileJava NO-SOURCE
> Task :build.foundation:support:processResources NO-SOURCE
> Task :build.foundation:support:classes UP-TO-DATE
> Task :build.foundation:support:jar UP-TO-DATE
> Task :build.foundation:structure:compileKotlin UP-TO-DATE
> Task :build.foundation:dependencies:compileKotlin UP-TO-DATE
> Task :build.foundation:structure:compileJava NO-SOURCE
> Task :build.foundation:structure:classes UP-TO-DATE
> Task :build.foundation:structure:jar UP-TO-DATE
> Task :build.foundation:dependencies:compileJava NO-SOURCE
> Task :build.foundation:dependencies:classes UP-TO-DATE
> Task :build.foundation:foojay:compileKotlin UP-TO-DATE
> Task :build.foundation:foojay:compileJava NO-SOURCE
> Task :build.foundation:foojay:classes UP-TO-DATE
> Task :build.foundation:foojay:jar UP-TO-DATE
> Task :build.foundation:dependencies:jar UP-TO-DATE
> Task :build.foundation:complement:checkKotlinGradlePluginConfigurationErrors
> Task :build.foundation:complement:compileKotlin UP-TO-DATE
> Task :build.foundation:complement:compileJava NO-SOURCE
> Task :build.foundation:complement:pluginDescriptors UP-TO-DATE
> Task :build.foundation:complement:processResources UP-TO-DATE
> Task :build.foundation:complement:classes UP-TO-DATE
> Task :build.foundation:complement:jar UP-TO-DATE
> Task :conventions:support:checkKotlinGradlePluginConfigurationErrors
> Task :conventions:core:checkKotlinGradlePluginConfigurationErrors
> Task :conventions:support.kotlin-gradle-plugin:checkKotlinGradlePluginConfigurationErrors
> Task :conventions:support.kotlin-gradle-plugin:pluginDescriptors UP-TO-DATE
> Task :conventions:core:pluginDescriptors UP-TO-DATE
> Task :conventions:support:compileKotlin NO-SOURCE
> Task :conventions:support:compileJava NO-SOURCE
> Task :conventions:support.kotlin-gradle-plugin:processResources NO-SOURCE
> Task :conventions:core:processResources UP-TO-DATE
> Task :conventions:support:pluginDescriptors UP-TO-DATE
> Task :conventions:support:processResources NO-SOURCE
> Task :conventions:support:classes UP-TO-DATE
> Task :conventions:support:jar UP-TO-DATE
> Task :conventions:support.kotlin-gradle-plugin:compileKotlin UP-TO-DATE
> Task :conventions:support.kotlin-gradle-plugin:compileJava NO-SOURCE
> Task :conventions:support.kotlin-gradle-plugin:classes UP-TO-DATE
> Task :conventions:support.kotlin-gradle-plugin:jar UP-TO-DATE
> Task :conventions:core:compileKotlin UP-TO-DATE
> Task :conventions:core:compileJava NO-SOURCE
> Task :conventions:core:classes UP-TO-DATE
> Task :conventions:core:jar UP-TO-DATE
> Task :conventions:commonizeNativeDistribution UP-TO-DATE
> Task :conventions:commonize UP-TO-DATE
> Task :conventions:prepareKotlinIdeaImport UP-TO-DATE
> Task :conventions:testing:commonize UP-TO-DATE
> Task :conventions:testing:prepareKotlinIdeaImport UP-TO-DATE
> Task :inclusives:commonizeNativeDistribution UP-TO-DATE
> Task :inclusives:commonize UP-TO-DATE
> Task :inclusives:prepareKotlinIdeaImport UP-TO-DATE
> Task :commonizeNativeDistribution UP-TO-DATE
> Task :commonize UP-TO-DATE
> Task :prepareKotlinIdeaImport UP-TO-DATE
> Task :sample:commonize UP-TO-DATE
> Task :inclusives:testing:commonize UP-TO-DATE
> Task :sample:prepareKotlinIdeaImport UP-TO-DATE
> Task :kokoro:internal.scoping:prepareKotlinIdeaImport UP-TO-DATE
> Task :prepareKotlinBuildScriptModel UP-TO-DATE
> Task :inclusives:testing:prepareKotlinIdeaImport UP-TO-DATE

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.5/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 1m 41s
44 actionable tasks: 9 executed, 35 up-to-date

Now, notice that from the above, :prepareKotlinBuildScriptModel is only ever run once. However, the above has included builds, which are, :build.foundation, :build.foundation:core, :conventions, inclusives, etc., each of which have their own root project, and yet, :prepareKotlinBuildScriptModel is not run for any of them. However, :prepareKotlinIdeaImport is run consistently for each project.

I also noticed that, :prepareKotlinIdeaImport consistently runs every time, but ONLY if you have it already created (manually), unlike :prepareKotlinBuildScriptModel.

Regarding my previous comment, I created a demo repo, which I noticed that, consistently reproduces the issue for composite builds.

See, https://github.com/jasonsparc/someBuildConfigWithIncludedBuild

Also, my IDE is as follows:

Android Studio Hedgehog | 2023.1.1 Patch 1
Build #AI-231.9392.1.2311.11255304, built on December 27, 2023
Runtime version: 17.0.7+0-b2043.56-10550314 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.

The build script that sets up your plugin is at, https://github.com/jasonsparc/someBuildConfigWithIncludedBuild/blob/master/someIncludedBuild/build.gradle.kts


Try to also uncomment the commented code at,

https://github.com/jasonsparc/someBuildConfigWithIncludedBuild/blob/master/someIncludedBuild/build.gradle.kts#L12

…and notice that IDE syncs now auto-generate the build configs every time.

Thanks for reporting this @jasonsparc!

Actually, I didn't mention how the "generate at sync" was implemented because I consider it a hack (but it works).

To build this properly, I'd also need to develop an IntelliJ plugin that will request a task, just like prepareKotlinBuildScriptModel does.

I'm going to try hooking on both tasks prepareKotlinIdeaImport and/or prepareKotlinBuildScriptModel, if they exist.

Based on what you have researched, it will fix it.

Thanks for the sample repo by the way, I was able to reproduce both issues (this and #120).

Regarding this one, I've confirmed this logic works as expected:

// generate at sync
afterEvaluate {
    if (extension.generateAtSync.get()) {
        tasks.maybeCreate("prepareKotlinIdeaImport").dependsOn(tasks.withType<BuildConfigTask>())
    }
}

I'm going to release a new version soon.