Force rerun a task by another task

is there a way I could make a task re-run again if another task depends on it and that it has been executed by gradle before?

in root project i have made this configuration

afterEvaluate {
   tasks.getByPath(":home:compileDebugKotlin").dependsOn(":service:home:compileDebugKotlin")
   tasks.getByPath(":subscription:compileDebugKotlin").dependsOn(":service:home:compileDebugKotlin")
}

after :home:compileDebugKotlin is executed (this means that :service:compileDebugKotlin was executed) I want :subscription:compileDebugKotlin to re-run :service:compileDebugKotlin once again.

The reason I need this is that home and subscription modules will generate some code using kapt and place it in service and if service gets compiled the bytecode will not include the generated classes simply because the didn’t exist when the compilation happened.

Gradle doesn’t work like this. A task will never run twice in a single Gradle invocation.

Instead of trying to execute a single task twice (with different config) you should create two tasks of the same type but each with different config/dependencies.

Think of the java plugin which adds two JavaCompile tasks, “compileJava” and “compileTestJava”. It does NOT execute the same compile task twice

1 Like

@Lance I understood that the hard way, and how gradle works after I saw your comment in the other thread. Let me put some context here:

I am developing a gradle plugin that will handle navigation in multi module project. I have put all the basic blocs there and everything works like charm. As I am finalising the plugin I wanted to automate everything for the users so they don’t have to configure anything (plug and enjoy).

My plugin works this way:

  1. in a project (user project) there is a sub-project named service this will handle the routing part of the navigation.
  2. there could be n other sub-projects that depend on service. They Have also classes annotated with some annotation which describes the specs of the navigation (destination, args…)
  3. since i am configuring kapt compiler into these sub projects they will run the task kaptDebugCompile and generate some source files. The annotation processor will output the files into the service subproject build directory.
  4. All sub projects that depend on service will then reference these generated classes from within `service.

Now when i compile the demo of the plugin, the compilation fails cuz service was compiled before kapt tasksfinished executing. Therefore the compiledservice` has no class definition of these generated classes.

I need a way to make :service:compileDebugKotlin depend on :someProject:kaptDebugKotlin. It’s sad that I can’t do that because :someProject:kaptDebugKotlin it self depends (by default) on :service:compileDebugKotlin becase someProject compiles service.

I know no way how to create kaptDebugKotlin with different config/params.

can you suggest something or point me to the right direction in case i am wrong with something?

Thanks a lot

I’m not familiar with Kotlin but I have a deep understanding of Gradle / Java.

i am configuring kapt compiler into these sub projects they will run the task kaptDebugCompile and generate some source files. The annotation processor will output the files into the service subproject build directory.

Don’t do that, each subproject should only put files in its own build directory. If one project wants to “communicate” with another it should be done via artifacts or configurations.

I’m guessing you should have this:

  1. Service project
  2. Subprojects depend on service project
  3. Subprojects generate files to their own build directories
  4. Subprojects have an extra configuration (or archive) which points to the generated files
  5. Have another project which collects all of the generated files (or compiled classes)

You could do 5 in the service project. You’d just need to use configurations to break the chicken/egg problem.

@Lance The essence of service sub project is to enable other subproject to know about available destination in different subproject without having to depend on them. So everyone depend on service and they get to know about all available destination in the app.

If I implement your suggest I would end up with just a duplicate of service which will lead to the same blocker again.

I am pretty interested in this particular point I think it shines some promises. Do you know how my configuration would look like?

You need to break the chicken or egg problem. Use configurations to solve the problem.

project(':service') {
   configurations {
      allgenerated
   } 
   dependencies {
      allgenerated project(path: ':a', configuration: 'generated')
      allgenerated project(path: ':b', configuration: 'generated')
      allgenerated project(path: ':c', configuration: 'generated') 
   } 
} 
project(':a') {
   configurations {
      generated 
      allgenerated
   }
   dependencies {
      generated files({tasks.generate}) 
      allgenerated project(path: ':service', configuration: 'allgenerated')
   }
   task generate {
      outputs.dir "$buildDir/generated" 
      doLast {
         // this task just generates for project 'a' 
      } 
   }
   task doStuffWithAllGenerated {
      dependsOn configurations.allgenerated 
      doLast {
           // this task can "see" all the generated stuff 
           configurations.allgenerated.each {...}
       } 
   } 
}
1 Like

@Lance Muchas gracias!
I will translate this into java gradle dsl api since i am configuring all subproject from the plugin apply function, and get back to your with a result. Hopefully this will break the spell

@Lance Unfortunately I am getting this exception

A problem occurred evaluating project ':service'.
> Could not find method allgenerated() for arguments [DefaultProjectDependency{dependencyProject='project ':a'', configuration='generated'}] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.

Did you add this

project(':service') {
   configurations {
      allgenerated
   } 
1 Like

My bad I have declared allGenerated in :service and consumed allgenerated in :a

Btw you seem to have an amazing understanding of gradle Kuddos!!. Could you please suggest me some resources to understand the usage of artifacts with gradle? the official docs don’t explain in details

I just start at Project.getArtifacts() which leads to ArtifactsHandler

1 Like

I will have a look Thanks a lot.

Btw you mentioned earlier that i should create a task that compiles service with different config/dependency. The default task to do that is compileDebugJavaWithJavac for java sources and compileDebugKotlin for kotlin. Since I work with kotlin do you know how can I extend that task and pass the service source files after allgenerated is done working?

Try adding

println "***${tasks.compileDebugKotlin.class.name}" 

You’ll see what task type it is. Or you could go to the android plugin docs which should tell you the type of the task. Then you’ll probably add another one to the model

I executed this CLI command which gives info about the task ./gradlew help --task :service:compileDebugKotlin here is the output:

> Task :help
Detailed task information for :service:compileDebugKotlin

Path
     :service:compileDebugKotlin

Type
     KotlinCompile (org.jetbrains.kotlin.gradle.tasks.KotlinCompile)

Description
     Compiles the debug kotlin.

Group
     -

@Lance here the source code of that task

As I said, I’m not fluent in kotlin. TBH I find the source quite ugly but that’s just me.

I’m assuming you’ll need two compile tasks in the service project. One for src/main/kotlin and another for the allgenerated sources.

I’m also assuming that the kotlin plugin works in the same way as the java plugin. Which means if you add another SourceSet that it will automatically add a KotlinCompile task to the model.

So perhaps you could do:

project(':service') {
   configurations {
      allgenerated
   } 
   sourceSets {
      allgenerated { // this adds a SourceSet which adds the compileAllgeneratedKotlin task to the model 
         kotlin.srcDir "$buildDir/allgenerated" 
      } 
   } 
   dependencies { 
     // as above 
   } 
   // compile tasks usually work with directories so 
   // might need to copy the configuration to a known directory 
   task copyAllGenerated(type:Copy) {
      from configurations.allgenerated
      into "$buildDir/allgenerated" 
   } 
   // wire the copy task into the DAG
   // task name might be different 
   compileAllgeneratedKotlin.dependsOn 'copyAllGenerated' 
} 

Or perhaps you want the extra compile step in each of the subprojects? And the service project collects the class files and builds a jar?

Eg

project(':a') {
   configurations { generated }
   sourceSets {
      generated {
         kotlin.srcDir "$buildDir/generated"
      }
   } 
   dependencies {
      generated tasks.compileGeneratedKotlin
   } 
   task generate {
      outputs.dir "$buildDir/generated" 
      ... 
   } 
   compileGeneratedKotlin.dependsOn 'generate' 
   ...
} 

I think the second approach is cleaner since the extra copy task isn’t required (the directory is “known” to the compile tasks since generate and compile are in the same project)

Another thing to consider: You could split your generate task into two tasks.

The first task (in each subproject) uses the complied classes to generate an xml/json file with all the annotation info.

The second task (in the service project) iterates all of the xml/json files and generates the kotlin (and subsequently compiles it)

I created my plugin to use PSI api from JetBrains to parse all kotlin files and gather the annotations info then generate all the classes. That hit the wall due to the complexity of reading annotation’s params.

So I have switched back to the idiomatic way which is to leverage the power of the Annotation Processing and use its API to read and manipulate/generate the necessary source files.

Getting back to your previous suggestions, I have tried them all but I am still facing the same problem. Also adding a SourceSet to android is not that trivial cuz it requires adding an extra buildType which certainly creates all the default compile tasks but it will not recompile the service project for some reason.

We have gone so far and deep though this issue so getting back to roots sounds like a good thing to me. The original problem occurs when :service:CompileDebugKotlin task (the task responsible for compiling the source code in service module) is getting executed before any of the dependant module got the chance to generate their source (A.K.A executed their annotation processors.).

If there is a way to delay the service from compiling it sources just until all dependant modules have finalised their annotation processing task (kaptDebugCompile) and copied the file back to service build folder. Now when service runs the task compileDebugKotlin it will be aware of the new files and everything will work fine later when the dependant modules will execute their own compileDebugKotlin because they will see the binaries compiled in service.

I am sorry if my problem frustrated you, sure thing it did that to me and still does since a month now. I can’t really let this go cuz I feel that it’s pretty possible to achieve if I manage to figure our the correct project config.

Million thanks @Lance you’ve been brilliant :slight_smile: