Dagger: Gradle incremental compilation doesn't detect changes to transitively included modules

Created on 11 Mar 2019  路  8Comments  路  Source: google/dagger

We have been using incremental compilation with Gradle for a few months, and we find it's effective in a majority of cases. However, we found that it fails to detect changes to the types bound with @Binds or the elements in a multibound set or map. One simple way to repro is to build a Dagger module that contains a single binding like this:

@Module
abstract class DaggerModule {
  @Binds
  abstract Base provideBase(SubclassB subclass);
}

Replacing SubclassA with SubclassB here won't cause the component to be regenerated. I created a small sample to facilitate a repro: https://github.com/cesar1000/dagger-incremental-bug.

I'm not sure whether this is a bug in Gradle or Dagger - @oehme and @autonomousapps, can you take a look?

Most helpful comment

This is a bug in Gradle. I've pushed a fix, which will be in 5.4. This doesn't just affect Dagger, but is a general problem with how we handle transitive dependencies. I guess it was never detected because usually the intermediate project fails when you remove some API. But in this case it just removes one of many bindings, so everything was still valid.

All 8 comments

Your example has an odd directory structure:

src/main/java/**src**/com/twitter/example

If I correct that and remove the duplicate "src" folder, everything works as expected. Not sure why the Java compiler allows this weird structure, since it means that your packages don't match the directory structure.

Ah, sometimes it's the simple things :). We did see an issue in the past with a class in the wrong directory, but it's reasonable it would fail in that case.

The issue we ran into was related to multibindings, and I tried to take a shortcut. Let me see if I can repro that one instead.

I repro'ed again in our codebase and extracted a minimal sample that does reproduce the issue. I don't think it's related to bindings or multibindings, and it does look like a gradle bug: given Dagger modules A, B and C, if A includes B and B includes C, changes to C cause a recompilation of B, but the change to B doesn't cause a recompilation of A. The issue only seems to repro when A, B and C are located in different gradle modules, all using incremental compilation.

This is the output that I get after updating the module in lib3:

> Task :lib3:compileJava
Task ':lib3:compileJava' is not up-to-date because:
  Input property 'source' file /Users/cpuerta/temp/dagger_incremental/lib3/src/main/java/com/twitter/example/DaggerModule.java has changed.
Created classpath snapshot for incremental compilation in 0.0 secs.
Class dependency analysis for incremental compilation took 0.0 secs.
Compiling with JDK Java compiler API.
Incremental compilation of 1 classes completed in 0.214 secs.
:lib3:compileJava (Thread[Execution worker for ':',5,main]) completed. Took 0.226 secs.
:lib3:processResources (Thread[Execution worker for ':',5,main]) started.

> Task :lib2:compileJava
Task ':lib2:compileJava' is not up-to-date because:
  Input property 'classpath' file /Users/cpuerta/temp/dagger_incremental/lib3/build/libs/lib3.jar has changed.
Created classpath snapshot for incremental compilation in 0.001 secs.
Class dependency analysis for incremental compilation took 0.0 secs.
Compiling with JDK Java compiler API.
Incremental compilation of 1 classes completed in 0.194 secs.
:lib2:compileJava (Thread[Execution worker for ':',5,main]) completed. Took 0.201 secs.
:lib2:processResources (Thread[Execution worker for ':',5,main]) started.

> Task :lib:compileJava UP-TO-DATE
Task ':lib:compileJava' is not up-to-date because:
  Input property 'classpath' file /Users/cpuerta/temp/dagger_incremental/lib3/build/libs/lib3.jar has changed.
Created classpath snapshot for incremental compilation in 0.0 secs.
Class dependency analysis for incremental compilation took 0.001 secs.
None of the classes needs to be compiled! Analysis took 0.002 secs.
:lib:compileJava (Thread[Execution worker for ':',5,main]) completed. Took 0.011 secs.

One single class was compiled in both lib3 and lib2, as expected, but lib was found to be up to date.

I suppose this is due to @Binds, and the consequence of it that DaggerModule is not referenced in the generated code.

When Gradle analyzes the API dependencies of lib classes, it doesn't see any change. I'd suppose that if you change the @Binds for a @Provides it would work the way you expect, because the generated code would then call that method, so there would be an API dependency from the generated code to the transitive module.

Could Dagger possibly fail to "declare" the module as a source element? Or Gradle ignores it?

I'll debug this. Gradle should see a change if the modules reference each other using annotations.

This is a bug in Gradle. I've pushed a fix, which will be in 5.4. This doesn't just affect Dagger, but is a general problem with how we handle transitive dependencies. I guess it was never detected because usually the intermediate project fails when you remove some API. But in this case it just removes one of many bindings, so everything was still valid.

Awesome, thanks! :)

Thanks!

Was this page helpful?
0 / 5 - 0 ratings