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.aggregateddeps; 18 19 import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; 20 import static com.google.auto.common.MoreElements.asType; 21 import static com.google.auto.common.MoreElements.getPackage; 22 import static com.google.common.collect.Iterables.getOnlyElement; 23 import static dagger.hilt.processor.internal.HiltCompilerOptions.isModuleInstallInCheckDisabled; 24 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 25 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 26 import static javax.lang.model.element.ElementKind.CLASS; 27 import static javax.lang.model.element.ElementKind.INTERFACE; 28 import static javax.lang.model.element.Modifier.ABSTRACT; 29 import static javax.lang.model.element.Modifier.STATIC; 30 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; 31 32 import com.google.auto.service.AutoService; 33 import com.google.common.collect.ImmutableList; 34 import com.google.common.collect.ImmutableSet; 35 import com.squareup.javapoet.ClassName; 36 import dagger.hilt.processor.internal.BaseProcessor; 37 import dagger.hilt.processor.internal.ClassNames; 38 import dagger.hilt.processor.internal.Components; 39 import dagger.hilt.processor.internal.ProcessorErrors; 40 import dagger.hilt.processor.internal.Processors; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Optional; 44 import java.util.Set; 45 import javax.annotation.processing.Processor; 46 import javax.lang.model.element.AnnotationMirror; 47 import javax.lang.model.element.AnnotationValue; 48 import javax.lang.model.element.Element; 49 import javax.lang.model.element.ExecutableElement; 50 import javax.lang.model.element.Name; 51 import javax.lang.model.element.TypeElement; 52 import javax.lang.model.type.TypeKind; 53 import javax.lang.model.util.ElementFilter; 54 import javax.lang.model.util.SimpleAnnotationValueVisitor8; 55 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; 56 57 /** Processor that outputs dummy files to propagate information through multiple javac runs. */ 58 @IncrementalAnnotationProcessor(ISOLATING) 59 @AutoService(Processor.class) 60 public final class AggregatedDepsProcessor extends BaseProcessor { 61 62 private static final ImmutableSet<ClassName> ENTRY_POINT_ANNOTATIONS = 63 ImmutableSet.of( 64 ClassNames.ENTRY_POINT, 65 ClassNames.EARLY_ENTRY_POINT, 66 ClassNames.GENERATED_ENTRY_POINT, 67 ClassNames.COMPONENT_ENTRY_POINT); 68 69 private static final ImmutableSet<ClassName> MODULE_ANNOTATIONS = 70 ImmutableSet.of( 71 ClassNames.MODULE); 72 73 private static final ImmutableSet<ClassName> INSTALL_IN_ANNOTATIONS = 74 ImmutableSet.of(ClassNames.INSTALL_IN, ClassNames.TEST_INSTALL_IN); 75 76 private final Set<Element> seen = new HashSet<>(); 77 78 @Override getSupportedAnnotationTypes()79 public Set<String> getSupportedAnnotationTypes() { 80 return ImmutableSet.builder() 81 .addAll(INSTALL_IN_ANNOTATIONS) 82 .addAll(MODULE_ANNOTATIONS) 83 .addAll(ENTRY_POINT_ANNOTATIONS) 84 .build() 85 .stream() 86 .map(Object::toString) 87 .collect(toImmutableSet()); 88 } 89 90 @Override processEach(TypeElement annotation, Element element)91 public void processEach(TypeElement annotation, Element element) throws Exception { 92 if (!seen.add(element)) { 93 return; 94 } 95 96 Optional<ClassName> installInAnnotation = getAnnotation(element, INSTALL_IN_ANNOTATIONS); 97 Optional<ClassName> entryPointAnnotation = getAnnotation(element, ENTRY_POINT_ANNOTATIONS); 98 Optional<ClassName> moduleAnnotation = getAnnotation(element, MODULE_ANNOTATIONS); 99 100 boolean hasInstallIn = installInAnnotation.isPresent(); 101 boolean isEntryPoint = entryPointAnnotation.isPresent(); 102 boolean isModule = moduleAnnotation.isPresent(); 103 104 ProcessorErrors.checkState( 105 !hasInstallIn || isEntryPoint || isModule, 106 element, 107 "@%s-annotated classes must also be annotated with @Module or @EntryPoint: %s", 108 installInAnnotation.map(ClassName::simpleName).orElse("@InstallIn"), 109 element); 110 111 ProcessorErrors.checkState( 112 !(isEntryPoint && isModule), 113 element, 114 "@%s and @%s cannot be used on the same interface: %s", 115 moduleAnnotation.map(ClassName::simpleName).orElse("@Module"), 116 entryPointAnnotation.map(ClassName::simpleName).orElse("@EntryPoint"), 117 element); 118 119 if (isModule) { 120 processModule(element, installInAnnotation, moduleAnnotation.get()); 121 } else if (isEntryPoint) { 122 processEntryPoint(element, installInAnnotation, entryPointAnnotation.get()); 123 } else { 124 throw new AssertionError(); 125 } 126 } 127 processModule( Element element, Optional<ClassName> installInAnnotation, ClassName moduleAnnotation)128 private void processModule( 129 Element element, Optional<ClassName> installInAnnotation, ClassName moduleAnnotation) 130 throws Exception { 131 ProcessorErrors.checkState( 132 installInAnnotation.isPresent() 133 || isDaggerGeneratedModule(element) 134 || installInCheckDisabled(element), 135 element, 136 "%s is missing an @InstallIn annotation. If this was intentional, see" 137 + " https://dagger.dev/hilt/flags#disable-install-in-check for how to disable this" 138 + " check.", 139 element); 140 141 if (!installInAnnotation.isPresent()) { 142 // Modules without @InstallIn or @TestInstallIn annotations don't need to be processed further 143 return; 144 } 145 146 ProcessorErrors.checkState( 147 element.getKind() == CLASS || element.getKind() == INTERFACE, 148 element, 149 "Only classes and interfaces can be annotated with @Module: %s", 150 element); 151 TypeElement module = asType(element); 152 153 ProcessorErrors.checkState( 154 Processors.isTopLevel(module) 155 || module.getModifiers().contains(STATIC) 156 || module.getModifiers().contains(ABSTRACT) 157 || Processors.hasAnnotation(module.getEnclosingElement(), ClassNames.HILT_ANDROID_TEST), 158 module, 159 "Nested @%s modules must be static unless they are directly nested within a test. " 160 + "Found: %s", 161 installInAnnotation.get().simpleName(), 162 module); 163 164 // Check that if Dagger needs an instance of the module, Hilt can provide it automatically by 165 // calling a visible empty constructor. 166 ProcessorErrors.checkState( 167 // Skip ApplicationContextModule, since Hilt manages this module internally. 168 ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module)) 169 || !Processors.requiresModuleInstance(getElementUtils(), module) 170 || Processors.hasVisibleEmptyConstructor(module), 171 module, 172 "Modules that need to be instantiated by Hilt must have a visible, empty constructor."); 173 174 // TODO(b/28989613): This should really be fixed in Dagger. Remove once Dagger bug is fixed. 175 ImmutableList<ExecutableElement> abstractMethodsWithMissingBinds = 176 ElementFilter.methodsIn(module.getEnclosedElements()).stream() 177 .filter(method -> method.getModifiers().contains(ABSTRACT)) 178 .filter(method -> !Processors.hasDaggerAbstractMethodAnnotation(method)) 179 .collect(toImmutableList()); 180 ProcessorErrors.checkState( 181 abstractMethodsWithMissingBinds.isEmpty(), 182 module, 183 "Found unimplemented abstract methods, %s, in an abstract module, %s. " 184 + "Did you forget to add a Dagger binding annotation (e.g. @Binds)?", 185 abstractMethodsWithMissingBinds, 186 module); 187 188 ImmutableList<TypeElement> replacedModules = ImmutableList.of(); 189 if (Processors.hasAnnotation(module, ClassNames.TEST_INSTALL_IN)) { 190 Optional<TypeElement> originatingTestElement = 191 Processors.getOriginatingTestElement(module, getElementUtils()); 192 ProcessorErrors.checkState( 193 !originatingTestElement.isPresent(), 194 // TODO(b/152801981): this should really error on the annotation value 195 module, 196 "@TestInstallIn modules cannot be nested in (or originate from) a " 197 + "@HiltAndroidTest-annotated class: %s", 198 originatingTestElement 199 .map(testElement -> testElement.getQualifiedName().toString()) 200 .orElse("")); 201 202 AnnotationMirror testInstallIn = 203 Processors.getAnnotationMirror(module, ClassNames.TEST_INSTALL_IN); 204 replacedModules = 205 Processors.getAnnotationClassValues(getElementUtils(), testInstallIn, "replaces"); 206 207 ProcessorErrors.checkState( 208 !replacedModules.isEmpty(), 209 // TODO(b/152801981): this should really error on the annotation value 210 module, 211 "@TestInstallIn#replaces() cannot be empty. Use @InstallIn instead."); 212 213 ImmutableList<TypeElement> nonInstallInModules = 214 replacedModules.stream() 215 .filter( 216 replacedModule -> 217 !Processors.hasAnnotation(replacedModule, ClassNames.INSTALL_IN)) 218 .collect(toImmutableList()); 219 220 ProcessorErrors.checkState( 221 nonInstallInModules.isEmpty(), 222 // TODO(b/152801981): this should really error on the annotation value 223 module, 224 "@TestInstallIn#replaces() can only contain @InstallIn modules, but found: %s", 225 nonInstallInModules); 226 227 ImmutableList<TypeElement> hiltWrapperModules = 228 replacedModules.stream() 229 .filter( 230 replacedModule -> 231 replacedModule.getSimpleName().toString().startsWith("HiltWrapper_")) 232 .collect(toImmutableList()); 233 234 ProcessorErrors.checkState( 235 hiltWrapperModules.isEmpty(), 236 // TODO(b/152801981): this should really error on the annotation value 237 module, 238 "@TestInstallIn#replaces() cannot contain Hilt generated public wrapper modules, " 239 + "but found: %s. ", 240 hiltWrapperModules); 241 242 if (!getPackage(module).getQualifiedName().toString().startsWith("dagger.hilt")) { 243 // Prevent external users from overriding Hilt's internal modules. Techincally, except for 244 // ApplicationContextModule, making all modules pkg-private should be enough but this is an 245 // extra measure of precaution. 246 ImmutableList<TypeElement> hiltInternalModules = 247 replacedModules.stream() 248 .filter( 249 replacedModule -> 250 getPackage(replacedModule) 251 .getQualifiedName() 252 .toString() 253 .startsWith("dagger.hilt")) 254 .collect(toImmutableList()); 255 256 ProcessorErrors.checkState( 257 hiltInternalModules.isEmpty(), 258 // TODO(b/152801981): this should really error on the annotation value 259 module, 260 "@TestInstallIn#replaces() cannot contain internal Hilt modules, but found: %s. ", 261 hiltInternalModules); 262 } 263 264 // Prevent users from uninstalling test-specific @InstallIn modules. 265 ImmutableList<TypeElement> replacedTestSpecificInstallIn = 266 replacedModules.stream() 267 .filter( 268 replacedModule -> 269 Processors.getOriginatingTestElement(replacedModule, getElementUtils()) 270 .isPresent()) 271 .collect(toImmutableList()); 272 273 ProcessorErrors.checkState( 274 replacedTestSpecificInstallIn.isEmpty(), 275 // TODO(b/152801981): this should really error on the annotation value 276 module, 277 "@TestInstallIn#replaces() cannot replace test specific @InstallIn modules, but found: " 278 + "%s. Please remove the @InstallIn module manually rather than replacing it.", 279 replacedTestSpecificInstallIn); 280 } 281 282 generateAggregatedDeps( 283 "modules", 284 module, 285 moduleAnnotation, 286 replacedModules.stream().map(ClassName::get).collect(toImmutableSet())); 287 } 288 processEntryPoint( Element element, Optional<ClassName> installInAnnotation, ClassName entryPointAnnotation)289 private void processEntryPoint( 290 Element element, Optional<ClassName> installInAnnotation, ClassName entryPointAnnotation) 291 throws Exception { 292 ProcessorErrors.checkState( 293 installInAnnotation.isPresent() , 294 element, 295 "@%s %s must also be annotated with @InstallIn", 296 entryPointAnnotation.simpleName(), 297 element); 298 299 ProcessorErrors.checkState( 300 !Processors.hasAnnotation(element, ClassNames.TEST_INSTALL_IN), 301 element, 302 "@TestInstallIn can only be used with modules"); 303 304 ProcessorErrors.checkState( 305 element.getKind() == INTERFACE, 306 element, 307 "Only interfaces can be annotated with @%s: %s", 308 entryPointAnnotation.simpleName(), 309 element); 310 TypeElement entryPoint = asType(element); 311 312 if (entryPointAnnotation.equals(ClassNames.EARLY_ENTRY_POINT)) { 313 ImmutableSet<ClassName> components = Components.getComponents(getElementUtils(), element); 314 ProcessorErrors.checkState( 315 components.equals(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)), 316 element, 317 "@EarlyEntryPoint can only be installed into the SingletonComponent. Found: %s", 318 components); 319 320 Optional<TypeElement> optionalTestElement = 321 Processors.getOriginatingTestElement(element, getElementUtils()); 322 ProcessorErrors.checkState( 323 !optionalTestElement.isPresent(), 324 element, 325 "@EarlyEntryPoint-annotated entry point, %s, cannot be nested in (or originate from) " 326 + "a @HiltAndroidTest-annotated class, %s. This requirement is to avoid confusion " 327 + "with other, test-specific entry points.", 328 asType(element).getQualifiedName().toString(), 329 optionalTestElement 330 .map(testElement -> testElement.getQualifiedName().toString()) 331 .orElse("")); 332 } 333 334 generateAggregatedDeps( 335 entryPointAnnotation.equals(ClassNames.COMPONENT_ENTRY_POINT) 336 ? "componentEntryPoints" 337 : "entryPoints", 338 entryPoint, 339 entryPointAnnotation, 340 ImmutableSet.of()); 341 } 342 generateAggregatedDeps( String key, TypeElement element, ClassName annotation, ImmutableSet<ClassName> replacedModules)343 private void generateAggregatedDeps( 344 String key, 345 TypeElement element, 346 ClassName annotation, 347 ImmutableSet<ClassName> replacedModules) 348 throws Exception { 349 // Get @InstallIn components here to catch errors before skipping user's pkg-private element. 350 ImmutableSet<ClassName> components = Components.getComponents(getElementUtils(), element); 351 352 if (isValidKind(element)) { 353 Optional<PkgPrivateMetadata> pkgPrivateMetadata = 354 PkgPrivateMetadata.of(getElementUtils(), element, annotation); 355 if (pkgPrivateMetadata.isPresent()) { 356 if (key.contentEquals("modules")) { 357 new PkgPrivateModuleGenerator(getProcessingEnv(), pkgPrivateMetadata.get()).generate(); 358 } else { 359 new PkgPrivateEntryPointGenerator(getProcessingEnv(), pkgPrivateMetadata.get()) 360 .generate(); 361 } 362 } else { 363 Optional<ClassName> testName = 364 Processors.getOriginatingTestElement(element, getElementUtils()).map(ClassName::get); 365 new AggregatedDepsGenerator( 366 key, element, testName, components, replacedModules, getProcessingEnv()) 367 .generate(); 368 } 369 } 370 } 371 getAnnotation( Element element, ImmutableSet<ClassName> annotations)372 private static Optional<ClassName> getAnnotation( 373 Element element, ImmutableSet<ClassName> annotations) { 374 ImmutableSet<ClassName> usedAnnotations = 375 annotations.stream() 376 .filter(annotation -> Processors.hasAnnotation(element, annotation)) 377 .collect(toImmutableSet()); 378 379 if (usedAnnotations.isEmpty()) { 380 return Optional.empty(); 381 } 382 383 ProcessorErrors.checkState( 384 usedAnnotations.size() == 1, 385 element, 386 "Only one of the following annotations can be used on %s: %s", 387 element, 388 usedAnnotations); 389 390 return Optional.of(getOnlyElement(usedAnnotations)); 391 } 392 isValidKind(Element element)393 private static boolean isValidKind(Element element) { 394 // don't go down the rabbit hole of analyzing undefined types. N.B. we don't issue 395 // an error here because javac already has and we don't want to spam the user. 396 return element.asType().getKind() != TypeKind.ERROR; 397 } 398 installInCheckDisabled(Element element)399 private boolean installInCheckDisabled(Element element) { 400 return isModuleInstallInCheckDisabled(getProcessingEnv()) 401 || Processors.hasAnnotation(element, ClassNames.DISABLE_INSTALL_IN_CHECK); 402 } 403 404 /** 405 * When using Dagger Producers, don't process generated modules. They will not have the expected 406 * annotations. 407 */ isDaggerGeneratedModule(Element element)408 private static boolean isDaggerGeneratedModule(Element element) { 409 if (!Processors.hasAnnotation(element, ClassNames.MODULE)) { 410 return false; 411 } 412 return element.getAnnotationMirrors().stream() 413 .filter(mirror -> isGenerated(mirror)) 414 .map(mirror -> asString(getOnlyElement(asList(getAnnotationValue(mirror, "value"))))) 415 .anyMatch(value -> value.startsWith("dagger")); 416 } 417 asList(AnnotationValue value)418 private static List<? extends AnnotationValue> asList(AnnotationValue value) { 419 return value.accept( 420 new SimpleAnnotationValueVisitor8<List<? extends AnnotationValue>, Void>() { 421 @Override 422 public List<? extends AnnotationValue> visitArray( 423 List<? extends AnnotationValue> value, Void unused) { 424 return value; 425 } 426 }, 427 null); 428 } 429 430 private static String asString(AnnotationValue value) { 431 return value.accept( 432 new SimpleAnnotationValueVisitor8<String, Void>() { 433 @Override 434 public String visitString(String value, Void unused) { 435 return value; 436 } 437 }, 438 null); 439 } 440 441 private static boolean isGenerated(AnnotationMirror annotationMirror) { 442 Name name = asType(annotationMirror.getAnnotationType().asElement()).getQualifiedName(); 443 return name.contentEquals("javax.annotation.Generated") 444 || name.contentEquals("javax.annotation.processing.Generated"); 445 } 446 } 447