• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.hilt.processor.internal.aggregateddeps;
18 
19 import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
20 import static com.google.auto.common.MoreElements.asType;
21 import static com.google.auto.common.MoreElements.getPackage;
22 import static com.google.common.collect.Iterables.getOnlyElement;
23 import static dagger.hilt.android.processor.internal.androidentrypoint.HiltCompilerOptions.BooleanOption.DISABLE_MODULES_HAVE_INSTALL_IN_CHECK;
24 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
25 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
26 import static javax.lang.model.element.ElementKind.CLASS;
27 import static javax.lang.model.element.ElementKind.INTERFACE;
28 import static javax.lang.model.element.Modifier.ABSTRACT;
29 import static javax.lang.model.element.Modifier.PRIVATE;
30 import static javax.lang.model.element.Modifier.STATIC;
31 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
32 
33 import com.google.auto.service.AutoService;
34 import com.google.common.collect.ImmutableList;
35 import com.google.common.collect.ImmutableSet;
36 import com.squareup.javapoet.ClassName;
37 import dagger.hilt.processor.internal.BaseProcessor;
38 import dagger.hilt.processor.internal.ClassNames;
39 import dagger.hilt.processor.internal.Components;
40 import dagger.hilt.processor.internal.KotlinMetadataUtils;
41 import dagger.hilt.processor.internal.ProcessorErrors;
42 import dagger.hilt.processor.internal.Processors;
43 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Optional;
47 import java.util.Set;
48 import javax.annotation.processing.Processor;
49 import javax.lang.model.element.AnnotationMirror;
50 import javax.lang.model.element.AnnotationValue;
51 import javax.lang.model.element.Element;
52 import javax.lang.model.element.ExecutableElement;
53 import javax.lang.model.element.Name;
54 import javax.lang.model.element.TypeElement;
55 import javax.lang.model.type.TypeKind;
56 import javax.lang.model.util.ElementFilter;
57 import javax.lang.model.util.SimpleAnnotationValueVisitor8;
58 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
59 
60 /** Processor that outputs dummy files to propagate information through multiple javac runs. */
61 @IncrementalAnnotationProcessor(ISOLATING)
62 @AutoService(Processor.class)
63 public final class AggregatedDepsProcessor extends BaseProcessor {
64 
65   private static final ImmutableSet<ClassName> ENTRY_POINT_ANNOTATIONS =
66       ImmutableSet.of(
67           ClassNames.ENTRY_POINT,
68           ClassNames.GENERATED_ENTRY_POINT,
69           ClassNames.COMPONENT_ENTRY_POINT);
70 
71   private static final ImmutableSet<ClassName> MODULE_ANNOTATIONS =
72       ImmutableSet.of(
73           ClassNames.MODULE);
74 
75   private static final ImmutableSet<ClassName> INSTALL_IN_ANNOTATIONS =
76       ImmutableSet.of(ClassNames.INSTALL_IN, ClassNames.TEST_INSTALL_IN);
77 
78   private final Set<Element> seen = new HashSet<>();
79 
80   @Override
getSupportedAnnotationTypes()81   public Set<String> getSupportedAnnotationTypes() {
82     return ImmutableSet.builder()
83         .addAll(INSTALL_IN_ANNOTATIONS)
84         .addAll(MODULE_ANNOTATIONS)
85         .addAll(ENTRY_POINT_ANNOTATIONS)
86         .build()
87         .stream()
88         .map(Object::toString)
89         .collect(toImmutableSet());
90   }
91 
92   @Override
processEach(TypeElement annotation, Element element)93   public void processEach(TypeElement annotation, Element element) throws Exception {
94     if (!seen.add(element)) {
95       return;
96     }
97 
98     Optional<ClassName> installInAnnotation = getAnnotation(element, INSTALL_IN_ANNOTATIONS);
99     Optional<ClassName> entryPointAnnotation = getAnnotation(element, ENTRY_POINT_ANNOTATIONS);
100     Optional<ClassName> moduleAnnotation = getAnnotation(element, MODULE_ANNOTATIONS);
101 
102     boolean hasInstallIn = installInAnnotation.isPresent();
103     boolean isEntryPoint = entryPointAnnotation.isPresent();
104     boolean isModule = moduleAnnotation.isPresent();
105 
106     ProcessorErrors.checkState(
107         !hasInstallIn || isEntryPoint || isModule,
108         element,
109         "@%s-annotated classes must also be annotated with @Module or @EntryPoint: %s",
110         installInAnnotation.map(ClassName::simpleName).orElse("@InstallIn"),
111         element);
112 
113     ProcessorErrors.checkState(
114         !(isEntryPoint && isModule),
115         element,
116         "@%s and @%s cannot be used on the same interface: %s",
117         moduleAnnotation.map(ClassName::simpleName).orElse("@Module"),
118         entryPointAnnotation.map(ClassName::simpleName).orElse("@EntryPoint"),
119         element);
120 
121     if (isModule) {
122       processModule(element, installInAnnotation, moduleAnnotation.get());
123     } else if (isEntryPoint) {
124       processEntryPoint(element, installInAnnotation, entryPointAnnotation.get());
125     } else {
126       throw new AssertionError();
127     }
128   }
129 
processModule( Element element, Optional<ClassName> installInAnnotation, ClassName moduleAnnotation)130   private void processModule(
131       Element element, Optional<ClassName> installInAnnotation, ClassName moduleAnnotation)
132       throws Exception {
133     ProcessorErrors.checkState(
134         installInAnnotation.isPresent()
135             || isDaggerGeneratedModule(element)
136             || installInCheckDisabled(element),
137         element,
138         "%s is missing an @InstallIn annotation. If this was intentional, see"
139             + " https://dagger.dev/hilt/compiler-options#disable-install-in-check for how to disable this"
140             + " check.",
141         element);
142 
143     if (!installInAnnotation.isPresent()) {
144       // Modules without @InstallIn or @TestInstallIn annotations don't need to be processed further
145       return;
146     }
147 
148     ProcessorErrors.checkState(
149         element.getKind() == CLASS || element.getKind() == INTERFACE,
150         element,
151         "Only classes and interfaces can be annotated with @Module: %s",
152         element);
153     TypeElement module = asType(element);
154 
155     ProcessorErrors.checkState(
156         Processors.isTopLevel(module)
157             || module.getModifiers().contains(STATIC)
158             || module.getModifiers().contains(ABSTRACT)
159             || Processors.hasAnnotation(module.getEnclosingElement(), ClassNames.HILT_ANDROID_TEST),
160         module,
161         "Nested @%s modules must be static unless they are directly nested within a test. "
162             + "Found: %s",
163         installInAnnotation.get().simpleName(),
164         module);
165 
166     // Check that if Dagger needs an instance of the module, Hilt can provide it automatically by
167     // calling a visible empty constructor.
168     ProcessorErrors.checkState(
169         !daggerRequiresModuleInstance(module) || hasVisibleEmptyConstructor(module),
170         module,
171         "Modules that need to be instantiated by Hilt must have a visible, empty constructor.");
172 
173     // TODO(b/28989613): This should really be fixed in Dagger. Remove once Dagger bug is fixed.
174     ImmutableList<ExecutableElement> abstractMethodsWithMissingBinds =
175         ElementFilter.methodsIn(module.getEnclosedElements()).stream()
176             .filter(method -> method.getModifiers().contains(ABSTRACT))
177             .filter(method -> !Processors.hasDaggerAbstractMethodAnnotation(method))
178             .collect(toImmutableList());
179     ProcessorErrors.checkState(
180         abstractMethodsWithMissingBinds.isEmpty(),
181         module,
182         "Found unimplemented abstract methods, %s, in an abstract module, %s. "
183             + "Did you forget to add a Dagger binding annotation (e.g. @Binds)?",
184         abstractMethodsWithMissingBinds,
185         module);
186 
187     ImmutableList<TypeElement> replacedModules = ImmutableList.of();
188     if (Processors.hasAnnotation(module, ClassNames.TEST_INSTALL_IN)) {
189       Optional<TypeElement> originatingTestElement = getOriginatingTestElement(module);
190       ProcessorErrors.checkState(
191           !originatingTestElement.isPresent(),
192           // TODO(b/152801981): this should really error on the annotation value
193           module,
194           "@TestInstallIn modules cannot be nested in (or originate from) a "
195                 + "@HiltAndroidTest-annotated class:  %s",
196           originatingTestElement
197               .map(testElement -> testElement.getQualifiedName().toString())
198               .orElse(""));
199 
200       AnnotationMirror testInstallIn =
201           Processors.getAnnotationMirror(module, ClassNames.TEST_INSTALL_IN);
202       replacedModules =
203           Processors.getAnnotationClassValues(getElementUtils(), testInstallIn, "replaces");
204 
205       ProcessorErrors.checkState(
206           !replacedModules.isEmpty(),
207           // TODO(b/152801981): this should really error on the annotation value
208           module,
209           "@TestInstallIn#replaces() cannot be empty. Use @InstallIn instead.");
210 
211       ImmutableList<TypeElement> nonInstallInModules =
212           replacedModules.stream()
213               .filter(
214                   replacedModule ->
215                       !Processors.hasAnnotation(replacedModule, ClassNames.INSTALL_IN))
216               .collect(toImmutableList());
217 
218       ProcessorErrors.checkState(
219           nonInstallInModules.isEmpty(),
220           // TODO(b/152801981): this should really error on the annotation value
221           module,
222           "@TestInstallIn#replaces() can only contain @InstallIn modules, but found: %s",
223           nonInstallInModules);
224 
225       ImmutableList<TypeElement> hiltWrapperModules =
226           replacedModules.stream()
227               .filter(
228                   replacedModule ->
229                       replacedModule.getSimpleName().toString().startsWith("HiltWrapper_"))
230               .collect(toImmutableList());
231 
232       ProcessorErrors.checkState(
233           hiltWrapperModules.isEmpty(),
234           // TODO(b/152801981): this should really error on the annotation value
235           module,
236           "@TestInstallIn#replaces() cannot contain Hilt generated public wrapper modules, "
237               + "but found: %s. ",
238           hiltWrapperModules);
239 
240       if (!getPackage(module).getQualifiedName().toString().startsWith("dagger.hilt")) {
241         // Prevent external users from overriding Hilt's internal modules. Techincally, except for
242         // ApplicationContextModule, making all modules pkg-private should be enough but this is an
243         // extra measure of precaution.
244         ImmutableList<TypeElement> hiltInternalModules =
245             replacedModules.stream()
246                 .filter(
247                     replacedModule ->
248                         getPackage(replacedModule)
249                             .getQualifiedName()
250                             .toString()
251                             .startsWith("dagger.hilt"))
252                 .collect(toImmutableList());
253 
254         ProcessorErrors.checkState(
255             hiltInternalModules.isEmpty(),
256             // TODO(b/152801981): this should really error on the annotation value
257             module,
258             "@TestInstallIn#replaces() cannot contain internal Hilt modules, but found: %s. ",
259             hiltInternalModules);
260       }
261 
262       // Prevent users from uninstalling test-specific @InstallIn modules.
263       ImmutableList<TypeElement> replacedTestSpecificInstallIn =
264           replacedModules.stream()
265               .filter(replacedModule -> getOriginatingTestElement(replacedModule).isPresent())
266               .collect(toImmutableList());
267 
268       ProcessorErrors.checkState(
269           replacedTestSpecificInstallIn.isEmpty(),
270           // TODO(b/152801981): this should really error on the annotation value
271           module,
272           "@TestInstallIn#replaces() cannot replace test specific @InstallIn modules, but found: "
273               + "%s. Please remove the @InstallIn module manually rather than replacing it.",
274           replacedTestSpecificInstallIn);
275     }
276 
277     generateAggregatedDeps(
278         "modules",
279         module,
280         moduleAnnotation,
281         replacedModules.stream().map(ClassName::get).collect(toImmutableSet()));
282   }
283 
processEntryPoint( Element element, Optional<ClassName> installInAnnotation, ClassName entryPointAnnotation)284   private void processEntryPoint(
285       Element element, Optional<ClassName> installInAnnotation, ClassName entryPointAnnotation)
286       throws Exception {
287     ProcessorErrors.checkState(
288         installInAnnotation.isPresent() ,
289         element,
290         "@%s %s must also be annotated with @InstallIn",
291         entryPointAnnotation.simpleName(),
292         element);
293 
294     ProcessorErrors.checkState(
295         !Processors.hasAnnotation(element, ClassNames.TEST_INSTALL_IN),
296         element,
297         "@TestInstallIn can only be used with modules");
298 
299     ProcessorErrors.checkState(
300         element.getKind() == INTERFACE,
301         element,
302         "Only interfaces can be annotated with @%s: %s",
303         entryPointAnnotation.simpleName(),
304         element);
305     TypeElement entryPoint = asType(element);
306 
307     generateAggregatedDeps(
308         entryPointAnnotation.equals(ClassNames.COMPONENT_ENTRY_POINT)
309             ? "componentEntryPoints"
310             : "entryPoints",
311         entryPoint,
312         entryPointAnnotation,
313         ImmutableSet.of());
314   }
315 
generateAggregatedDeps( String key, TypeElement element, ClassName annotation, ImmutableSet<ClassName> replacedModules)316   private void generateAggregatedDeps(
317       String key,
318       TypeElement element,
319       ClassName annotation,
320       ImmutableSet<ClassName> replacedModules)
321       throws Exception {
322     // Get @InstallIn components here to catch errors before skipping user's pkg-private element.
323     ImmutableSet<ClassName> components = Components.getComponents(getElementUtils(), element);
324 
325     if (isValidKind(element)) {
326       Optional<PkgPrivateMetadata> pkgPrivateMetadata =
327           PkgPrivateMetadata.of(getElementUtils(), element, annotation);
328       if (pkgPrivateMetadata.isPresent()) {
329         if (key.contentEquals("modules")) {
330           new PkgPrivateModuleGenerator(getProcessingEnv(), pkgPrivateMetadata.get()).generate();
331         } else {
332           new PkgPrivateEntryPointGenerator(getProcessingEnv(), pkgPrivateMetadata.get())
333               .generate();
334         }
335       } else {
336         Optional<ClassName> testName = getOriginatingTestElement(element).map(ClassName::get);
337         new AggregatedDepsGenerator(
338                 key, element, testName, components, replacedModules, getProcessingEnv())
339             .generate();
340       }
341     }
342   }
343 
getAnnotation( Element element, ImmutableSet<ClassName> annotations)344   private static Optional<ClassName> getAnnotation(
345       Element element, ImmutableSet<ClassName> annotations) {
346     ImmutableSet<ClassName> usedAnnotations =
347         annotations.stream()
348             .filter(annotation -> Processors.hasAnnotation(element, annotation))
349             .collect(toImmutableSet());
350 
351     if (usedAnnotations.isEmpty()) {
352       return Optional.empty();
353     }
354 
355     ProcessorErrors.checkState(
356         usedAnnotations.size() == 1,
357         element,
358         "Only one of the following annotations can be used on %s: %s",
359         element,
360         usedAnnotations);
361 
362     return Optional.of(getOnlyElement(usedAnnotations));
363   }
364 
getOriginatingTestElement(Element element)365   private Optional<TypeElement> getOriginatingTestElement(Element element) {
366     TypeElement topLevelType = getOriginatingTopLevelType(element);
367     return Processors.hasAnnotation(topLevelType, ClassNames.HILT_ANDROID_TEST)
368         ? Optional.of(asType(topLevelType))
369         : Optional.empty();
370   }
371 
getOriginatingTopLevelType(Element element)372   private TypeElement getOriginatingTopLevelType(Element element) {
373     TypeElement topLevelType = Processors.getTopLevelType(element);
374     if (Processors.hasAnnotation(topLevelType, ClassNames.ORIGINATING_ELEMENT)) {
375       return getOriginatingTopLevelType(
376           Processors.getAnnotationClassValue(
377               getElementUtils(),
378               Processors.getAnnotationMirror(topLevelType, ClassNames.ORIGINATING_ELEMENT),
379               "topLevelClass"));
380     }
381     return topLevelType;
382   }
383 
isValidKind(Element element)384   private static boolean isValidKind(Element element) {
385     // don't go down the rabbit hole of analyzing undefined types. N.B. we don't issue
386     // an error here because javac already has and we don't want to spam the user.
387     return element.asType().getKind() != TypeKind.ERROR;
388   }
389 
installInCheckDisabled(Element element)390   private boolean installInCheckDisabled(Element element) {
391     return DISABLE_MODULES_HAVE_INSTALL_IN_CHECK.get(getProcessingEnv())
392         || Processors.hasAnnotation(element, ClassNames.DISABLE_INSTALL_IN_CHECK);
393   }
394 
395   /**
396    * When using Dagger Producers, don't process generated modules. They will not have the expected
397    * annotations.
398    */
isDaggerGeneratedModule(Element element)399   private static boolean isDaggerGeneratedModule(Element element) {
400     if (!Processors.hasAnnotation(element, ClassNames.MODULE)) {
401       return false;
402     }
403     return element.getAnnotationMirrors().stream()
404         .filter(mirror -> isGenerated(mirror))
405         .map(mirror -> asString(getOnlyElement(asList(getAnnotationValue(mirror, "value")))))
406         .anyMatch(value -> value.startsWith("dagger"));
407   }
408 
asList(AnnotationValue value)409   private static List<? extends AnnotationValue> asList(AnnotationValue value) {
410     return value.accept(
411         new SimpleAnnotationValueVisitor8<List<? extends AnnotationValue>, Void>() {
412           @Override
413           public List<? extends AnnotationValue> visitArray(
414               List<? extends AnnotationValue> value, Void unused) {
415             return value;
416           }
417         },
418         null);
419   }
420 
421   private static String asString(AnnotationValue value) {
422     return value.accept(
423         new SimpleAnnotationValueVisitor8<String, Void>() {
424           @Override
425           public String visitString(String value, Void unused) {
426             return value;
427           }
428         },
429         null);
430   }
431 
432   private static boolean isGenerated(AnnotationMirror annotationMirror) {
433     Name name = asType(annotationMirror.getAnnotationType().asElement()).getQualifiedName();
434     return name.contentEquals("javax.annotation.Generated")
435         || name.contentEquals("javax.annotation.processing.Generated");
436   }
437 
438   private static boolean daggerRequiresModuleInstance(TypeElement module) {
439     return !module.getModifiers().contains(ABSTRACT)
440         && !hasOnlyStaticProvides(module)
441         // Skip ApplicationContextModule, since Hilt manages this module internally.
442         && !ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module))
443         // Skip Kotlin object modules since all their provision methods are static
444         && !isKotlinObject(module);
445   }
446 
447   private static boolean isKotlinObject(TypeElement type) {
448     KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil();
449     return metadataUtil.isObjectClass(type) || metadataUtil.isCompanionObjectClass(type);
450   }
451 
452   private static boolean hasOnlyStaticProvides(TypeElement module) {
453     // TODO(erichang): Check for @Produces too when we have a producers story
454     return ElementFilter.methodsIn(module.getEnclosedElements()).stream()
455         .filter(method -> Processors.hasAnnotation(method, ClassNames.PROVIDES))
456         .allMatch(method -> method.getModifiers().contains(STATIC));
457   }
458 
459   private static boolean hasVisibleEmptyConstructor(TypeElement type) {
460     List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
461     return constructors.isEmpty()
462         || constructors.stream()
463             .filter(constructor -> constructor.getParameters().isEmpty())
464             .anyMatch(
465                 constructor ->
466                     !constructor.getModifiers().contains(PRIVATE)
467                         );
468   }
469 }
470