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