• 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.internal.codegen.extension.DaggerStreams.toImmutableList;
21 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
22 import static javax.lang.model.element.Modifier.PUBLIC;
23 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.AGGREGATING;
24 
25 import com.google.auto.common.MoreElements;
26 import com.google.auto.service.AutoService;
27 import com.google.common.collect.ImmutableList;
28 import com.google.common.collect.ImmutableSet;
29 import com.squareup.javapoet.ClassName;
30 import dagger.hilt.processor.internal.BaseProcessor;
31 import dagger.hilt.processor.internal.ComponentTree;
32 import dagger.hilt.processor.internal.ProcessorErrors;
33 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies;
34 import dagger.hilt.processor.internal.definecomponent.DefineComponents;
35 import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs;
36 import java.io.IOException;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Set;
42 import javax.annotation.processing.ProcessingEnvironment;
43 import javax.annotation.processing.Processor;
44 import javax.annotation.processing.RoundEnvironment;
45 import javax.lang.model.element.Element;
46 import javax.lang.model.element.TypeElement;
47 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
48 
49 /** Processor that outputs dagger components based on transitive build deps. */
50 @IncrementalAnnotationProcessor(AGGREGATING)
51 @AutoService(Processor.class)
52 public final class RootProcessor extends BaseProcessor {
53   private final List<ClassName> rootNames = new ArrayList<>();
54   private final Set<ClassName> processed = new HashSet<>();
55   private boolean isTestEnv;
56   // TODO(bcorso): Consider using a Dagger component to create/scope these objects
57   private final DefineComponents defineComponents = DefineComponents.create();
58   private GeneratesRootInputs generatesRootInputs;
59 
60   @Override
init(ProcessingEnvironment processingEnvironment)61   public synchronized void init(ProcessingEnvironment processingEnvironment) {
62     super.init(processingEnvironment);
63     generatesRootInputs = new GeneratesRootInputs(processingEnvironment);
64   }
65 
66   @Override
getSupportedAnnotationTypes()67   public ImmutableSet<String> getSupportedAnnotationTypes() {
68     return ImmutableSet.<String>builder()
69         .addAll(
70             Arrays.stream(RootType.values())
71                 .map(rootType -> rootType.className().toString())
72                 .collect(toImmutableSet()))
73         .build();
74   }
75 
76   @Override
processEach(TypeElement annotation, Element element)77   public void processEach(TypeElement annotation, Element element) throws Exception {
78     TypeElement rootElement = MoreElements.asType(element);
79     boolean isTestRoot = RootType.of(getProcessingEnv(), rootElement).isTestRoot();
80     checkState(
81         rootNames.isEmpty() || isTestEnv == isTestRoot,
82         "Cannot mix test roots with non-test roots:"
83             + "\n\tNon-Test Roots: %s"
84             + "\n\tTest Roots: %s",
85         isTestRoot ? rootNames : rootElement,
86         isTestRoot ? rootElement : rootNames);
87     isTestEnv = isTestRoot;
88 
89     rootNames.add(ClassName.get(rootElement));
90     if (isTestEnv) {
91       new TestInjectorGenerator(
92           getProcessingEnv(),
93           TestRootMetadata.of(getProcessingEnv(), rootElement)).generate();
94     } else {
95       ProcessorErrors.checkState(
96           rootNames.size() <= 1, element, "More than one root found: %s", rootNames);
97     }
98   }
99 
100   @Override
postRoundProcess(RoundEnvironment roundEnv)101   public void postRoundProcess(RoundEnvironment roundEnv) throws Exception {
102     Set<Element> newElements = generatesRootInputs.getElementsToWaitFor(roundEnv);
103     if (!processed.isEmpty() ) {
104       checkState(
105           newElements.isEmpty(),
106           "Found extra modules after compilation: %s\n"
107               + "(If you are adding an annotation processor that generates root input for hilt, "
108               + "the annotation must be annotated with @dagger.hilt.GeneratesRootInput.\n)",
109           newElements);
110     }
111 
112     if (!newElements.isEmpty()) {
113       // Skip further processing since there's new elements that generate root inputs in this round.
114       return;
115     }
116 
117     ImmutableList<Root> rootsToProcess =
118         rootNames.stream()
119             .filter(rootName -> !processed.contains(rootName))
120             // We create a new root element each round to avoid the jdk8 bug where
121             // TypeElement.equals does not work for elements across processing rounds.
122             .map(rootName -> getElementUtils().getTypeElement(rootName.toString()))
123             .map(rootElement -> Root.create(rootElement, getProcessingEnv()))
124             .collect(toImmutableList());
125 
126     if (rootsToProcess.isEmpty()) {
127       // Skip further processing since there's no roots that need processing.
128       return;
129     }
130 
131     // TODO(bcorso): Currently, if there's an exception in any of the roots we stop processing
132     // all roots. We should consider if it's worth trying to continue processing for other
133     // roots. At the moment, I think it's rare that if one root failed the others would not.
134     try {
135       ComponentTree tree = defineComponents.getComponentTree(getElementUtils());
136       ComponentDependencies deps = ComponentDependencies.from(
137           tree.getComponentDescriptors(), getElementUtils());
138       ImmutableList<RootMetadata> rootMetadatas =
139           rootsToProcess.stream()
140               .map(root -> RootMetadata.create(root, tree, deps, getProcessingEnv()))
141               .collect(toImmutableList());
142 
143       for (RootMetadata rootMetadata : rootMetadatas) {
144         setProcessingState(rootMetadata.root());
145         generateComponents(rootMetadata);
146       }
147 
148       if (isTestEnv) {
149         generateTestComponentData(rootMetadatas);
150       }
151     } catch (Exception e) {
152       for (Root root : rootsToProcess) {
153         processed.add(root.classname());
154       }
155       throw e;
156     }
157   }
158 
setProcessingState(Root root)159   private void setProcessingState(Root root) {
160     processed.add(root.classname());
161   }
162 
generateComponents(RootMetadata rootMetadata)163   private void generateComponents(RootMetadata rootMetadata) throws IOException {
164     RootGenerator.generate(rootMetadata, getProcessingEnv());
165   }
166 
generateTestComponentData(ImmutableList<RootMetadata> rootMetadatas)167   private void generateTestComponentData(ImmutableList<RootMetadata> rootMetadatas)
168       throws IOException {
169     for (RootMetadata rootMetadata : rootMetadatas) {
170       // TODO(bcorso): Consider moving this check earlier into processEach.
171       TypeElement testElement = rootMetadata.testRootMetadata().testElement();
172       ProcessorErrors.checkState(
173           testElement.getModifiers().contains(PUBLIC),
174           testElement,
175           "Hilt tests must be public, but found: %s",
176           testElement);
177       new TestComponentDataGenerator(getProcessingEnv(), rootMetadata).generate();
178     }
179     new TestComponentDataSupplierGenerator(getProcessingEnv(), rootMetadatas).generate();
180   }
181 }
182