• 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.root;
18 
19 import static com.google.common.base.Preconditions.checkState;
20 import static dagger.hilt.processor.internal.HiltCompilerOptions.isCrossCompilationRootValidationDisabled;
21 import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled;
22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
24 import static java.util.Comparator.comparing;
25 import static javax.lang.model.element.Modifier.PUBLIC;
26 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.AGGREGATING;
27 
28 import com.google.auto.common.MoreElements;
29 import com.google.auto.service.AutoService;
30 import com.google.common.collect.ImmutableList;
31 import com.google.common.collect.ImmutableSet;
32 import com.squareup.javapoet.ClassName;
33 import dagger.hilt.processor.internal.BaseProcessor;
34 import dagger.hilt.processor.internal.ClassNames;
35 import dagger.hilt.processor.internal.ComponentDescriptor;
36 import dagger.hilt.processor.internal.ComponentNames;
37 import dagger.hilt.processor.internal.ProcessorErrors;
38 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies;
39 import dagger.hilt.processor.internal.definecomponent.DefineComponents;
40 import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs;
41 import java.io.IOException;
42 import java.util.Arrays;
43 import java.util.Comparator;
44 import java.util.HashSet;
45 import java.util.Set;
46 import javax.annotation.processing.ProcessingEnvironment;
47 import javax.annotation.processing.Processor;
48 import javax.annotation.processing.RoundEnvironment;
49 import javax.lang.model.element.Element;
50 import javax.lang.model.element.TypeElement;
51 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
52 
53 /** Processor that outputs dagger components based on transitive build deps. */
54 @IncrementalAnnotationProcessor(AGGREGATING)
55 @AutoService(Processor.class)
56 public final class RootProcessor extends BaseProcessor {
57   private static final Comparator<TypeElement> QUALIFIED_NAME_COMPARATOR =
58       comparing(TypeElement::getQualifiedName, (n1, n2) -> n1.toString().compareTo(n2.toString()));
59 
60   private final Set<ClassName> processed = new HashSet<>();
61   // TODO(bcorso): Consider using a Dagger component to create/scope these objects
62   private final DefineComponents defineComponents = DefineComponents.create();
63   private GeneratesRootInputs generatesRootInputs;
64 
65   @Override
init(ProcessingEnvironment processingEnvironment)66   public synchronized void init(ProcessingEnvironment processingEnvironment) {
67     super.init(processingEnvironment);
68     generatesRootInputs = new GeneratesRootInputs(processingEnvironment);
69   }
70 
71   @Override
getSupportedAnnotationTypes()72   public ImmutableSet<String> getSupportedAnnotationTypes() {
73     return ImmutableSet.<String>builder()
74         .addAll(
75             Arrays.stream(RootType.values())
76                 .map(rootType -> rootType.className().toString())
77                 .collect(toImmutableSet()))
78         .build();
79   }
80 
81   @Override
processEach(TypeElement annotation, Element element)82   public void processEach(TypeElement annotation, Element element) throws Exception {
83     TypeElement rootElement = MoreElements.asType(element);
84     RootType rootType = RootType.of(rootElement);
85     if (rootType.isTestRoot()) {
86       new TestInjectorGenerator(
87               getProcessingEnv(), TestRootMetadata.of(getProcessingEnv(), rootElement))
88           .generate();
89     }
90     new AggregatedRootGenerator(rootElement, annotation, getProcessingEnv()).generate();
91   }
92 
93   @Override
postRoundProcess(RoundEnvironment roundEnv)94   public void postRoundProcess(RoundEnvironment roundEnv) throws Exception {
95     Set<Element> newElements = generatesRootInputs.getElementsToWaitFor(roundEnv);
96     if (!processed.isEmpty() ) {
97       checkState(
98           newElements.isEmpty(),
99           "Found extra modules after compilation: %s\n"
100               + "(If you are adding an annotation processor that generates root input for hilt, "
101               + "the annotation must be annotated with @dagger.hilt.GeneratesRootInput.\n)",
102           newElements);
103     }
104 
105     if (!newElements.isEmpty()) {
106       // Skip further processing since there's new elements that generate root inputs in this round.
107       return;
108     }
109 
110     ImmutableSet<Root> allRoots =
111         AggregatedRootMetadata.from(getElementUtils()).stream()
112             .map(metadata -> Root.create(metadata.rootElement(), getProcessingEnv()))
113             .collect(toImmutableSet());
114 
115     ImmutableSet<Root> processedRoots =
116         ProcessedRootSentinelMetadata.from(getElementUtils()).stream()
117             .flatMap(metadata -> metadata.rootElements().stream())
118             .map(rootElement -> Root.create(rootElement, getProcessingEnv()))
119             .collect(toImmutableSet());
120 
121     ImmutableSet<Root> rootsToProcess =
122         allRoots.stream()
123             .filter(root -> !processedRoots.contains(root))
124             .filter(root -> !processed.contains(rootName(root)))
125             .collect(toImmutableSet());
126 
127     if (rootsToProcess.isEmpty()) {
128       // Skip further processing since there's no roots that need processing.
129       return;
130     }
131 
132     // TODO(bcorso): Currently, if there's an exception in any of the roots we stop processing
133     // all roots. We should consider if it's worth trying to continue processing for other
134     // roots. At the moment, I think it's rare that if one root failed the others would not.
135     try {
136       validateRoots(allRoots, rootsToProcess);
137 
138       boolean isTestEnv = rootsToProcess.stream().anyMatch(Root::isTestRoot);
139       ComponentNames componentNames =
140           isTestEnv && isSharedTestComponentsEnabled(getProcessingEnv())
141               ? ComponentNames.withRenamingIntoPackage(
142                   ClassNames.DEFAULT_ROOT.packageName(),
143                   rootsToProcess.stream().map(Root::element).collect(toImmutableList()))
144               : ComponentNames.withoutRenaming();
145 
146       ImmutableSet<ComponentDescriptor> componentDescriptors =
147           defineComponents.getComponentDescriptors(getElementUtils());
148       ComponentTree tree = ComponentTree.from(componentDescriptors);
149       ComponentDependencies deps =
150           ComponentDependencies.from(componentDescriptors, getElementUtils());
151       ImmutableList<RootMetadata> rootMetadatas =
152           rootsToProcess.stream()
153               .map(root -> RootMetadata.create(root, tree, deps, getProcessingEnv()))
154               .collect(toImmutableList());
155 
156       for (RootMetadata rootMetadata : rootMetadatas) {
157         if (!rootMetadata.canShareTestComponents()) {
158           generateComponents(rootMetadata, componentNames);
159         }
160       }
161 
162       if (isTestEnv) {
163         ImmutableList<RootMetadata> rootsThatCanShareComponents =
164             rootMetadatas.stream()
165                 .filter(RootMetadata::canShareTestComponents)
166                 .collect(toImmutableList());
167         generateTestComponentData(rootMetadatas, componentNames);
168         if (deps.hasEarlyEntryPoints() || !rootsThatCanShareComponents.isEmpty()) {
169           Root defaultRoot = Root.createDefaultRoot(getProcessingEnv());
170           generateComponents(
171               RootMetadata.createForDefaultRoot(
172                   defaultRoot, rootsThatCanShareComponents, tree, deps, getProcessingEnv()),
173               componentNames);
174           EarlySingletonComponentCreatorGenerator.generate(getProcessingEnv());
175         }
176       }
177     } catch (Exception e) {
178       for (Root root : rootsToProcess) {
179         processed.add(rootName(root));
180       }
181       throw e;
182     } finally {
183       rootsToProcess.forEach(this::setProcessingState);
184       // Calculate the roots processed in this round. We do this in the finally-block rather than in
185       // the try-block because the catch-block can change the processing state.
186       ImmutableSet<Root> rootsProcessedInRound =
187           rootsToProcess.stream()
188               // Only add a sentinel for processed roots. Skip preprocessed roots since those will
189               // will be processed in the next round.
190               .filter(root -> processed.contains(rootName(root)))
191               .collect(toImmutableSet());
192       for (Root root : rootsProcessedInRound) {
193         new ProcessedRootSentinelGenerator(rootElement(root), getProcessingEnv()).generate();
194       }
195     }
196   }
197 
validateRoots(ImmutableSet<Root> allRoots, ImmutableSet<Root> rootsToProcess)198   private void validateRoots(ImmutableSet<Root> allRoots, ImmutableSet<Root> rootsToProcess) {
199 
200     ImmutableSet<TypeElement> rootElementsToProcess =
201         rootsToProcess.stream()
202             .map(Root::element)
203             .sorted(QUALIFIED_NAME_COMPARATOR)
204             .collect(toImmutableSet());
205 
206     ImmutableSet<TypeElement> appRootElementsToProcess =
207         rootsToProcess.stream()
208             .filter(root -> !root.isTestRoot())
209             .map(Root::element)
210             .sorted(QUALIFIED_NAME_COMPARATOR)
211             .collect(toImmutableSet());
212 
213     // Perform validation between roots in this compilation unit.
214     if (!appRootElementsToProcess.isEmpty()) {
215       ImmutableSet<TypeElement> testRootElementsToProcess =
216           rootsToProcess.stream()
217               .filter(Root::isTestRoot)
218               .map(Root::element)
219               .sorted(QUALIFIED_NAME_COMPARATOR)
220               .collect(toImmutableSet());
221 
222       ProcessorErrors.checkState(
223           testRootElementsToProcess.isEmpty(),
224           "Cannot process test roots and app roots in the same compilation unit:"
225               + "\n\tApp root in this compilation unit: %s"
226               + "\n\tTest roots in this compilation unit: %s",
227           appRootElementsToProcess,
228           testRootElementsToProcess);
229 
230       ProcessorErrors.checkState(
231           appRootElementsToProcess.size() == 1,
232           "Cannot process multiple app roots in the same compilation unit: %s",
233           appRootElementsToProcess);
234     }
235 
236     // Perform validation across roots previous compilation units.
237     if (!isCrossCompilationRootValidationDisabled(rootElementsToProcess, getProcessingEnv())) {
238       ImmutableSet<TypeElement> processedTestRootElements =
239           allRoots.stream()
240               .filter(Root::isTestRoot)
241               .filter(root -> !rootsToProcess.contains(root))
242               .map(Root::element)
243               .sorted(QUALIFIED_NAME_COMPARATOR)
244               .collect(toImmutableSet());
245 
246       // TODO(b/185742783): Add an explanation or link to docs to explain why we're forbidding this.
247       ProcessorErrors.checkState(
248           processedTestRootElements.isEmpty(),
249           "Cannot process new roots when there are test roots from a previous compilation unit:"
250               + "\n\tTest roots from previous compilation unit: %s"
251               + "\n\tAll roots from this compilation unit: %s",
252           processedTestRootElements,
253           rootElementsToProcess);
254 
255       ImmutableSet<TypeElement> processedAppRootElements =
256           allRoots.stream()
257               .filter(root -> !root.isTestRoot())
258               .filter(root -> !rootsToProcess.contains(root))
259               .map(Root::element)
260               .sorted(QUALIFIED_NAME_COMPARATOR)
261               .collect(toImmutableSet());
262 
263       ProcessorErrors.checkState(
264           processedAppRootElements.isEmpty() || appRootElementsToProcess.isEmpty(),
265           "Cannot process app roots in this compilation unit since there are app roots in a "
266               + "previous compilation unit:"
267               + "\n\tApp roots in previous compilation unit: %s"
268               + "\n\tApp roots in this compilation unit: %s",
269           processedAppRootElements,
270           appRootElementsToProcess);
271     }
272   }
273 
setProcessingState(Root root)274   private void setProcessingState(Root root) {
275     processed.add(rootName(root));
276   }
277 
rootName(Root root)278   private ClassName rootName(Root root) {
279     return ClassName.get(rootElement(root));
280   }
281 
rootElement(Root root)282   private TypeElement rootElement(Root root) {
283     return root.element();
284   }
285 
generateComponents(RootMetadata rootMetadata, ComponentNames componentNames)286   private void generateComponents(RootMetadata rootMetadata, ComponentNames componentNames)
287       throws IOException {
288     RootGenerator.generate(rootMetadata, componentNames, getProcessingEnv());
289   }
290 
generateTestComponentData( ImmutableList<RootMetadata> rootMetadatas, ComponentNames componentNames)291   private void generateTestComponentData(
292       ImmutableList<RootMetadata> rootMetadatas, ComponentNames componentNames) throws IOException {
293     for (RootMetadata rootMetadata : rootMetadatas) {
294       // TODO(bcorso): Consider moving this check earlier into processEach.
295       TypeElement testElement = rootMetadata.testRootMetadata().testElement();
296       ProcessorErrors.checkState(
297           testElement.getModifiers().contains(PUBLIC),
298           testElement,
299           "Hilt tests must be public, but found: %s",
300           testElement);
301       new TestComponentDataGenerator(getProcessingEnv(), rootMetadata, componentNames).generate();
302     }
303   }
304 }
305