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