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