Plugin - How can I run a task before Gradle project is linked?

Answered
  • Our company works in Gradle git repositories that don't have a gradle or gradlew file on a fresh checkout.
  • Users are required to run an initialization script to pull a gradle wrapper and gradle-wrapper.properties file from a remote host before opening IDEA
  • IDEA works with previously initialized wrapper, but fails if users forget to run the script as IDEA will use a wrapper from services.gradle.org that doesn't contain information the repository needs

In a plugin, I would like to make this a seamless process for users by running the initialization script that sets up gradlew before Gradle will attempt to initialize the module and link it to the project. I need to do the following:

  1. Run script just before Gradle links module to project
  2. Run script on project open for Gradle projects already linked

I feel I'm on the right track but can't really find the right way to go about it. I've looked at:

ProjectOpenProcessor, GradleTaskManagerExtension, GradleProjectResolverExtension

0
6 comments

Please take a look at ExternalSystemExecutionAware.prepareExecution
Parameter `task: ExternalSystemTask` allows you to check if it is a reload (sync) or just Gradle task launch.
Parameter `taskNotificationListener: ExternalSystemTaskNotificationListener` allows to send events (text) to sync/build output toolwindow

1

This was exactly what we needed. Thanks Nikita!

We also added a quickfix option to detect invalid state of the Gradle project

All of this together looked something like this:

package my.gradle.execution

import my.gradle.progress.ExternalSystemTaskProgressIndicator
import my.gradle.progress.MyBeforeProjectSyncTaskBackgroundable
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
import com.intellij.openapi.progress.BackgroundTaskQueue
import com.intellij.openapi.project.Project
import org.jetbrains.plugins.gradle.service.execution.GradleExecutionAware
import com.intellij.build.issue.BuildIssue
import com.intellij.build.issue.BuildIssueQuickFix
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil

import com.intellij.pom.Navigatable
import org.jetbrains.plugins.gradle.util.GradleConstants
import java.util.concurrent.CompletableFuture

class MyGradleExecutionAware : GradleExecutionAware { override fun prepareExecution( task: ExternalSystemTask, externalProjectPath: String, isPreviewMode: Boolean, taskNotificationListener: ExternalSystemTaskNotificationListener, project: Project ) { if (isPreviewMode) return if (task.id.type != ExternalSystemTaskType.RESOLVE_PROJECT) return val progressIndicator = ExternalSystemTaskProgressIndicator( task, taskNotificationListener ) val myBeforeTask = MyBeforeProjectSyncTaskBackgroundable( project, externalProjectPath, progressIndicator ) val backgroundTaskQueue = BackgroundTaskQueue(project, "Running a task before setting up dependencies, etc") backgroundTaskQueue.run(myBeforeTask) backgroundTaskQueue.waitForTasksToFinish() } }


We also added a quickfix action to detect issues with Gradle sync

package my.gradle.issue.checker

import com.intellij.build.FilePosition
import com.intellij.build.events.BuildEvent
import com.intellij.build.events.MessageEvent
import com.intellij.build.events.impl.BuildIssueEventImpl
import com.intellij.build.issue.BuildIssue
import com.intellij.util.io.systemIndependentPath
import org.gradle.api.GradleException
import org.jetbrains.plugins.gradle.issue.GradleIssueChecker
import org.jetbrains.plugins.gradle.issue.GradleIssueData
import java.nio.file.Paths
import java.util.function.Consumer

open
class MyGradleIssueChecker() : GradleIssueChecker {

override fun consumeBuildOutputFailureMessage(
        message: String,
        failureCause: String,
        stacktrace: String?,
        location: FilePosition?,
        parentEventId: Any,
        messageConsumer: Consumer<in BuildEvent>
    ): Boolean {
      val projectPath = location?.file?.toPath()?.parent ?: return false
val exception = GradleException(message, Exception(failureCause))
val buildIssue = check(
GradleIssueDate(
projectPath.systemIndependentPath,
exception,
null,
location
)
) ?: return false
messageConsumer.accept(
BuildIssueEventImpl(
parentEventId,
buildIssue,
MessageEvent.Kind.ERROR
)
)
return true
}

override fun check(issueData: GradleIssueData): BuildIssue? {
val message = issueData.error.message ?: return null
val cause = issueData.error.cause?.message ?: return null
val projectPath = Paths.get(issueData.projectPath)

val shouldReturnIssue = true // assuming your check finds some issue

return if (shouldReturnIssue) {
          MyGradleInvalidStateBuildIssue(projectPath)
} else {
null
}
}

class MyGradleInvalidStateBuildIssue(projectPath: String) : BuildIssue {
override val description: String
get() = "My description of issue that occurred in $projectPath"
override val quickFixes: List<BuildIssueQuickFix>
get() = listOf(MyGradleQuickFix(projectName))
override val title: String
get() = "My invalid state title"
override fun getNavigatable(project: Project): Navigatable? = null
}

class MyGradleQuickFix(projectPath: String): BuildIssueQuickFix {
override val id: String = "MyGradleQuickFix"
override fun runQuickFix(project: Project, dataContext: DataContext): CompletableFuture<*> {
return CompletableFuture.supplyAsync {
// do something asynchronously
ExternalSystemUtil.refreshProject(
projectPath,
ImportSpecBuilder(project, GradleConstants.SYSTEM_ID).build()
)
}
}
}

 

plugin.xml

<idea-plugin>
    <depends>com.intellij.gradle</depends>
    <extensions defaultExtensionNs="com.intellij">
      <externalExecutionAware id="MyGradleExecutionAware"
                                key="GRADLE"
                                order="before gradle"
                              implementationClass="my.gradle.execution.MyGradleExecutionAware" />
    </extensions>
    <extensions defaultExtensionNs="org.jetbrains.plugins.gradle">
      <issueChecker implementation="my.gradle.issue.checker.MyGradleIssueChecker" />
    </extensions>
</idea-plugin>

 

References:

https://github.com/JetBrains/intellij-community/blob/9bbb7c809fda2bbeadf229a69b5ee145f6fb7ae7/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/GradleExecutionAware.kt

https://github.com/JetBrains/intellij-community/blob/idea/223.8617.56/plugins/gradle/src/org/jetbrains/plugins/gradle/issue/GradleIssueChecker.kt

 

 

0

Thank you for the followup!

0

Londonchaim Nikita Skvortsov I am trying to achieve something similar and seeing the following behavior using the code above for MyGradleExecutionAware - execution aware gets triggered during Gradle Sync but does not when a gradle task is invoked through IDE Run button. Is that expected or should this work for both Sync/Builds through IDE?

0

sshah  that is expected to work on both, Sync and Run tasks (Builds) through IDE.
There was a slight problem with starting tasks using double-click in the Gradle toolwindow: IDEA-298823 . Does it look like your case?

0

That is not the case for me. I am starting task with single-click on Run button in IDE. I was able to achieve my use case using GradleBuildListener. Still not sure why GradleExecutionAware worked for Sync but not Run.

0

Please sign in to leave a comment.