dagger: Processor error on missing type while traversing too far up component dependency chain.
Module ‘test-a’:
public final class A {
@Inject A() {}
}
@Component
public interface ComponentA {
A a();
}
Module ‘test-b’ which has implementation project(':test-a'):
public final class B {
@Inject B(A a) {}
}
@Component(dependencies = ComponentA.class)
public interface ComponentB {
B b();
}
Module ‘test-c’ which has implementation project(':test-b'):
public final class C {
@Inject C(B b) {}
}
@Component(dependencies = ComponentB.class)
public interface ComponentC {
C c();
}
fails with:
> Task :test-c:compileDebugJavaWithJavac FAILED
error: cannot access ComponentA
class file for test.a.ComponentA not found
Consult the following stack trace for details.
com.sun.tools.javac.code.Symbol$CompletionFailure: class file for test.a.ComponentA not found
1 error
which makes sense, because ‘test-c’ isn’t meant to see ‘test-a’ as it’s an implementation detail of ‘test-b’, but why is Dagger in ‘test-c’ trying to do anything with ComponentA? Once it reaches ComponentB and sees the required B type exposed shouldn’t it stop?
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 58
- Comments: 48
Commits related to this issue
- Import simple example This is a simple example to reproduce an issue where Dagger can't inject objects from transitive dependencies. See: https://github.com/google/dagger/issues/970 — committed to snepalnetflix/dagger-transitive-dep by snepalnetflix 5 years ago
- [Dagger]: Add a compiler option to prevent validating transitive component dependencies See https://github.com/google/dagger/issues/970 Fixes #970 RELNOTES=Fix #970: Add a compiler option to preven... — committed to google/dagger by bcorso 4 years ago
- [Dagger]: Add a compiler option to prevent validating transitive component dependencies See https://github.com/google/dagger/issues/970 Fixes #970 RELNOTES=Fix #970: Add a compiler option to preven... — committed to google/dagger by bcorso 4 years ago
We fully understand the issue you’re seeing, that’s not the problem.
I think the best recommendation I have now is not to use
implementationwith dagger components in your component dependencies chain.I’d like to bring this issue up again. I understand why it happens and why it isn’t purely Dagger’s fault. But the build tools (Gradle in this case) and Dagger don’t work well together. There are only two solutions at the moment for Gradle: Add the dependencies with
apito the library module and fully expose them (not great for modularization) or add missing dependencies ascompileOnly(or maybe evenannotationProcessor / kapt) to the modules that need them, what isn’t good for modularization and isolation either.I don’t have any hands-on experience with Blaze or Bazel, but I heard it can solve this issue with its finer granularity. But for many people those tools aren’t feasible.
How could Dagger avoid this issue? By turning off the cyclic dependency check?
Running into this again.
I think Dagger should support this case by giving up on validation when it reaches an annotation whose referenced types cannot be resolved. This means that the scope annotation of that referenced component dependency also almost certainly cannot be resolved in the current context as it’s been entirely encapsulated which means there can be no cycle. Even if there is a cycle, it’s hidden from the consumer and therefore irrelevant for what the check is enforcing. Correct semantics are still maintained.
This check is already best effort. There doesn’t seem to be a reason to make it fail in situations where the user is actually participating in the best practice of using
implementation.Here’s an updated workaround for Android.
Works for both
aptandkaptand properly wires task dependnecies. Works for AGP 3.5 and Kotlin 1.3.61It adds module runtime classpath to annotation processor classpath.
Hi @ronshapiro , we are also modularizing our app with reducing build time as one of the main goals and I can imagine more developers will run into same situation. So hope you guys can figure out a solution. Thanks!
@ronshapiro I bumped into the same issue - trying to modularize our app and having a component in each module. We currently have the main app module’s AppComponent depending on a library’s component in
datasourcemodule anddatasourceby itself depends on another library module that also exposes a component. We get the same compilation error.I tried to add scopes for each component, but that didn’t change anything.
I’ve recreated this setup in a sample app https://gitlab.com/codepond/dagger-modules
It would be great if you could have a look.
Thanks and Happy TuBiShvat! 😃
No problems so far.
Overhead and performance depend on your project setup. In theory: If you have only few root modules which generate
@Component’s(this is the only place whereaptRuntime2CompileClasspathshould be applied), and a module tree with jvm/aar which you switched toimplementation(because you were forced to useapifor dagger), overall build time of the whole project should slightly increase because of compilation avoidance and less compilation dependencies for each module.But it does see it, because it’s exposed on B’s signatures (both constructor and annotations). You as a human might not consider the constructor part of the API (because it’s not really meant to be called by others), but a machine can’t know that. A type is either exposed or not. There is no “partly exposed”. You’d have to hide it behind an interface to solve that. This is the workaround that Ron already pointed out.
Looking at the original example from Jake:
ComponentBreferencesComponentAwith an annotation, which makesComponentApart of its public signature. Even if it didn’t referenceComponentA, it is referencingBandBhasAin its public constructor signature. So using animplementationdependency is not right in this case, it should be anapidependency. All the other examples in this thread have the same issue. See also the user guide chapter on how to recognizeapiandimplementationusage of a dependency.@ronshapiro already provided a workaround of having an interface for your component and only making that interface part of your API while keeping the implementation separate. I don’t see any other way around this. It’s a price you pay for having everything statically analysed and generated, which requires having everything on the public signatures instead of making it an implementation detail (like you would inside a Guice module).
ronshapiro: what do you think about an opt-in flag to disable this validation. Would you be willing to upstream such a change?
Currently this error completely blocks using dagger with our multi-module application.
I would have thought you’d see an
ErrorTypeor something prior to this and that trying to resolve that type is what caused by exception.