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