Plugin task order on multi-module project

Hi,

I’m trying to add a new task to a gradle plugin to generate java source files.
I need this files to compile user’s source files, so I added compileJavaTask.mustRunAfter(myTask);.
And it is executed before compilation. The problem is, it is executed before compilation of all the modules. And some of the modules are required for the task run (I have application that depends on common and I need common jar during the task execution on application).
Build log for the test project: https://gist.github.com/michalszynkiewicz/822f0b054484a47b0daffcb575b02128

How can I make my task executed after :jar tasks for the dependency modules?

edit: I’m on Gradle 6.3

Thanks,
Michał

It seems I need to add dependencies on tasks from different modules to do that.
The best place for it seems to be in the afterEvaluate where all the tasks should be defined.
That what worked for me.

You shouldn’t really care about the :jar task of the dependency module. If you need an output artifact from a project, model that dependency, not the implementation detail of what creates that artifact.

Ideally, no. Model your artifact dependencies correctly and the tasks will execute as needed to fulfill those dependency requirements. The goal is to minimize configuration coupling between projects in a multi-project build.

The best case is to avoid afterEvaluate as its usage generally represents a code smell that would not be needed if implemented correctly, but can be a quick fix if you don’t control some of the less than ideal code in question.

I don’t know what your custom tasks require exactly, but ideally you get the JAR from an existing configuration or else you add one that specifically fulfills those requirements, if different.

Hi James,

Could you elaborate a bit on modeling dependencies to avoid adding dependencies on specific tasks? Or point me to some documentation?

Thanks,
Michał

Given a multi-project build for the project with modules application and common, it would be typical to see a build.gradle for application that contains a project dependency:

dependencies {
    implementation project('common')
}

If you were to run, gradle application:compileJava, the JAR for the common module would also be built along with any other dependencies that it has. There is no explicit dependency on the common module tasks, but the common module is configured to know how to build the JAR file and will do so if you declare a dependency on it.

It’s still unclear to me whether you simply need the classpath of everything in the application for your plugin’s custom task or if there’s just a JAR file or two that you need. If it’s the former, you can use the compileClasspath or the runtimeClasspath and it will function just as the rest of the build process. If it’s the latter, you can define your own configuration for these specific dependencies (disable transitive if you don’t need it):

configurations {
    quarkus
}

dependencies {
    quarkus project('common')
}

When you resolve the quarkus configuration to get the file paths, the common module will be built first due to the project lib dependency.

There’s useful documentation with these concepts (depending on how familiar you are with the basic concepts) in many different locations, but I think the most direct information is Multi-Project Builds - Project lib dependencies

The following section, Multi-Project Builds - Depending on the task output produced by another project, is not needed for the JAR file since it is already an output, but it does call out the point that “declaring a task dependency from one project to another is a poor way to model this kind of relationship and introduces unnecessary coupling.”

Thanks and sorry, gradle noob here, I don’t fully understand…

Let me explain what I’m doing: I am creating a gradle task that is generating sources (Java files).
What’s generated by the task depends on the dependencies of the project.
Appropriately annotated methods from the project’s dependencies get triggered by the task to generate sources.
To make that happen, I need dependencies to be “ready” when the source generation task is triggered.

An example of a user’s build.gradle of the application looks could look like: https://github.com/michalszynkiewicz/quarkus/blob/pre-build-grpc/devtools/gradle/src/functionalTest/resources/grpc-multi-module-project/application/build.gradle#L6

With such a configuration, a task in application that has no dependencies on other tasks gets triggered before the common module is built (I’m guessing the order here is indeterministic if there are no dependencies; whatever is ready, i.e. doesn’t have dependencies, gets executed).

Are you saying there’s a way of configuring a task to depend on output of a build of a dependency module (without depending on a specific task)?

Yes, this is generally how a multi-project build works. You declare a dependency on the project artifacts, not individual tasks on the project. For a typical Java project this is, by default, the JAR file. Ideally, the plugin leverages those same capabilities.

Based on the repository with the example it looks like the plugin itself you’re talking about is also open source. I didn’t look through all of the code there yet, but the plugin seems fairly mature and established. Depending on the situation there, previous decisions might make it more challenging to implement ideally without additional changes to the existing implementation, which might be more than you can tackle for the current change you want to make.