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