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