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