• 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;
18 
19 import static com.google.common.base.Preconditions.checkState;
20 
21 import com.google.auto.common.MoreElements;
22 import com.google.auto.value.AutoValue;
23 import com.google.common.collect.LinkedHashMultimap;
24 import com.google.common.collect.SetMultimap;
25 import com.squareup.javapoet.ClassName;
26 import java.util.Collection;
27 import java.util.LinkedHashSet;
28 import java.util.Map;
29 import java.util.Set;
30 import javax.annotation.processing.AbstractProcessor;
31 import javax.annotation.processing.Messager;
32 import javax.annotation.processing.ProcessingEnvironment;
33 import javax.annotation.processing.RoundEnvironment;
34 import javax.lang.model.SourceVersion;
35 import javax.lang.model.element.Element;
36 import javax.lang.model.element.TypeElement;
37 import javax.lang.model.util.Elements;
38 import javax.lang.model.util.Types;
39 
40 /**
41  * Implements default configurations for Processors, and provides structure for exception handling.
42  *
43  * <p>By default #process() will do the following:
44  *
45  * <ol>
46  *   <li> #preRoundProcess()
47  *   <li> foreach element:
48  *     <ul><li> #processEach()</ul>
49  *   </li>
50  *   <li> #postRoundProcess()
51  *   <li> #claimAnnotation()
52  * </ol>
53  *
54  * <p>#processEach() allows each element to be processed, even if exceptions are thrown. Due to the
55  * non-deterministic ordering of the processed elements, this is needed to ensure a consistent set
56  * of exceptions are thrown with each build.
57  */
58 public abstract class BaseProcessor extends AbstractProcessor {
59   /** Stores the state of processing for a given annotation and element. */
60   @AutoValue
61   abstract static class ProcessingState {
of(TypeElement annotation, Element element)62     private static ProcessingState of(TypeElement annotation, Element element) {
63       // We currently only support TypeElements directly annotated with the annotation.
64       // TODO(bcorso): Switch to using BasicAnnotationProcessor if we need more than this.
65       // Note: Switching to BasicAnnotationProcessor is currently not possible because of cyclic
66       // references to generated types in our API. For example, an @AndroidEntryPoint annotated
67       // element will indefinitely defer its own processing because it extends a generated type
68       // that it's responsible for generating.
69       checkState(MoreElements.isType(element));
70       checkState(Processors.hasAnnotation(element, ClassName.get(annotation)));
71       return new AutoValue_BaseProcessor_ProcessingState(
72           ClassName.get(annotation),
73           ClassName.get(MoreElements.asType(element)));
74     }
75 
76     /** Returns the class name of the annotation. */
annotationClassName()77     abstract ClassName annotationClassName();
78 
79     /** Returns the type name of the annotated element. */
elementClassName()80     abstract ClassName elementClassName();
81 
82     /** Returns the annotation that triggered the processing. */
annotation(Elements elements)83     TypeElement annotation(Elements elements) {
84       return elements.getTypeElement(elementClassName().toString());
85     }
86 
87     /** Returns the annotated element to process. */
element(Elements elements)88     TypeElement element(Elements elements) {
89       return elements.getTypeElement(annotationClassName().toString());
90     }
91   }
92 
93   private final Set<ProcessingState> stateToReprocess = new LinkedHashSet<>();
94   private Elements elements;
95   private Types types;
96   private Messager messager;
97   private ProcessorErrorHandler errorHandler;
98 
99   @Override
getSupportedOptions()100   public final Set<String> getSupportedOptions() {
101     // This is declared here rather than in the actual processors because KAPT will issue a
102     // warning if any used option is not unsupported. This can happen when there is a module
103     // which uses Hilt but lacks any @AndroidEntryPoint annotations.
104     // See: https://github.com/google/dagger/issues/2040
105     return HiltCompilerOptions.getProcessorOptions();
106   }
107 
108   /** Used to perform initialization before each round of processing. */
preRoundProcess(RoundEnvironment roundEnv)109   protected void preRoundProcess(RoundEnvironment roundEnv) {};
110 
111   /**
112    * Called for each element in a round that uses a supported annotation.
113    *
114    * Note that an exception can be thrown for each element in the round. This is usually preferred
115    * over throwing only the first exception in a round. Only throwing the first exception in the
116    * round can lead to flaky errors that are dependent on the non-deterministic ordering that the
117    * elements are processed in.
118    */
processEach(TypeElement annotation, Element element)119   protected void processEach(TypeElement annotation, Element element) throws Exception {};
120 
121   /**
122    * Used to perform post processing at the end of a round. This is especially useful for handling
123    * additional processing that depends on aggregate data, that cannot be handled in #processEach().
124    *
125    * <p>Note: this will not be called if an exception is thrown during #processEach() -- if we have
126    * already detected errors on an annotated element, performing post processing on an aggregate
127    * will just produce more (perhaps non-deterministic) errors.
128    */
postRoundProcess(RoundEnvironment roundEnv)129   protected void postRoundProcess(RoundEnvironment roundEnv) throws Exception {};
130 
131   /** @return true if you want to claim annotations after processing each round. Default false. */
claimAnnotations()132   protected boolean claimAnnotations() {
133     return false;
134   }
135 
136   /**
137    * @return true if you want to delay errors to the last round. Useful if the processor
138    * generates code for symbols used a lot in the user code. Delaying allows as much code to
139    * compile as possible for correctly configured types and reduces error spam.
140    */
delayErrors()141   protected boolean delayErrors() {
142     return false;
143   }
144 
145 
146   @Override
init(ProcessingEnvironment processingEnvironment)147   public synchronized void init(ProcessingEnvironment processingEnvironment) {
148     super.init(processingEnvironment);
149     this.messager = processingEnv.getMessager();
150     this.elements = processingEnv.getElementUtils();
151     this.types = processingEnv.getTypeUtils();
152     this.errorHandler = new ProcessorErrorHandler(processingEnvironment);
153   }
154 
155   @Override
getSupportedSourceVersion()156   public SourceVersion getSupportedSourceVersion() {
157     return SourceVersion.latestSupported();
158   }
159 
160   /**
161    * This should not be overridden, as it defines the order of the processing.
162    */
163   @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)164   public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
165     preRoundProcess(roundEnv);
166 
167     boolean roundError = false;
168 
169     // Gather the set of new and deferred elements to process, grouped by annotation.
170     SetMultimap<TypeElement, Element> elementMultiMap = LinkedHashMultimap.create();
171     for (ProcessingState processingState : stateToReprocess) {
172       elementMultiMap.put(processingState.annotation(elements), processingState.element(elements));
173     }
174     for (TypeElement annotation : annotations) {
175       elementMultiMap.putAll(annotation, roundEnv.getElementsAnnotatedWith(annotation));
176     }
177 
178     // Clear the processing state before reprocessing.
179     stateToReprocess.clear();
180 
181     for (Map.Entry<TypeElement, Collection<Element>> entry : elementMultiMap.asMap().entrySet()) {
182       TypeElement annotation = entry.getKey();
183       for (Element element : entry.getValue()) {
184         try {
185           processEach(annotation, element);
186         } catch (Exception e) {
187           if (e instanceof ErrorTypeException && !roundEnv.processingOver()) {
188             // Allow an extra round to reprocess to try to resolve this type.
189             stateToReprocess.add(ProcessingState.of(annotation, element));
190           } else {
191             errorHandler.recordError(e);
192             roundError = true;
193           }
194         }
195       }
196     }
197 
198     if (!roundError) {
199       try {
200         postRoundProcess(roundEnv);
201       } catch (Exception e) {
202         errorHandler.recordError(e);
203       }
204     }
205 
206     if (!delayErrors() || roundEnv.processingOver()) {
207       errorHandler.checkErrors();
208     }
209 
210     return claimAnnotations();
211   }
212 
213   /** @return the error handle for the processor. */
getErrorHandler()214   protected final ProcessorErrorHandler getErrorHandler() {
215     return errorHandler;
216   }
217 
getProcessingEnv()218   public final ProcessingEnvironment getProcessingEnv() {
219     return processingEnv;
220   }
221 
getElementUtils()222   public final Elements getElementUtils() {
223     return elements;
224   }
225 
getTypeUtils()226   public final Types getTypeUtils() {
227     return types;
228   }
229 
getMessager()230   public final Messager getMessager() {
231     return messager;
232   }
233 }
234