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.root; 18 19 import static com.google.common.base.Preconditions.checkState; 20 import static dagger.hilt.processor.internal.HiltCompilerOptions.isCrossCompilationRootValidationDisabled; 21 import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled; 22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 24 import static java.util.Comparator.comparing; 25 import static javax.lang.model.element.Modifier.PUBLIC; 26 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.AGGREGATING; 27 28 import com.google.auto.common.MoreElements; 29 import com.google.auto.service.AutoService; 30 import com.google.common.collect.ImmutableList; 31 import com.google.common.collect.ImmutableSet; 32 import com.squareup.javapoet.ClassName; 33 import dagger.hilt.processor.internal.BaseProcessor; 34 import dagger.hilt.processor.internal.ClassNames; 35 import dagger.hilt.processor.internal.ComponentDescriptor; 36 import dagger.hilt.processor.internal.ComponentNames; 37 import dagger.hilt.processor.internal.ProcessorErrors; 38 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; 39 import dagger.hilt.processor.internal.definecomponent.DefineComponents; 40 import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs; 41 import java.io.IOException; 42 import java.util.Arrays; 43 import java.util.Comparator; 44 import java.util.HashSet; 45 import java.util.Set; 46 import javax.annotation.processing.ProcessingEnvironment; 47 import javax.annotation.processing.Processor; 48 import javax.annotation.processing.RoundEnvironment; 49 import javax.lang.model.element.Element; 50 import javax.lang.model.element.TypeElement; 51 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; 52 53 /** Processor that outputs dagger components based on transitive build deps. */ 54 @IncrementalAnnotationProcessor(AGGREGATING) 55 @AutoService(Processor.class) 56 public final class RootProcessor extends BaseProcessor { 57 private static final Comparator<TypeElement> QUALIFIED_NAME_COMPARATOR = 58 comparing(TypeElement::getQualifiedName, (n1, n2) -> n1.toString().compareTo(n2.toString())); 59 60 private final Set<ClassName> processed = new HashSet<>(); 61 // TODO(bcorso): Consider using a Dagger component to create/scope these objects 62 private final DefineComponents defineComponents = DefineComponents.create(); 63 private GeneratesRootInputs generatesRootInputs; 64 65 @Override init(ProcessingEnvironment processingEnvironment)66 public synchronized void init(ProcessingEnvironment processingEnvironment) { 67 super.init(processingEnvironment); 68 generatesRootInputs = new GeneratesRootInputs(processingEnvironment); 69 } 70 71 @Override getSupportedAnnotationTypes()72 public ImmutableSet<String> getSupportedAnnotationTypes() { 73 return ImmutableSet.<String>builder() 74 .addAll( 75 Arrays.stream(RootType.values()) 76 .map(rootType -> rootType.className().toString()) 77 .collect(toImmutableSet())) 78 .build(); 79 } 80 81 @Override processEach(TypeElement annotation, Element element)82 public void processEach(TypeElement annotation, Element element) throws Exception { 83 TypeElement rootElement = MoreElements.asType(element); 84 RootType rootType = RootType.of(rootElement); 85 if (rootType.isTestRoot()) { 86 new TestInjectorGenerator( 87 getProcessingEnv(), TestRootMetadata.of(getProcessingEnv(), rootElement)) 88 .generate(); 89 } 90 new AggregatedRootGenerator(rootElement, annotation, getProcessingEnv()).generate(); 91 } 92 93 @Override postRoundProcess(RoundEnvironment roundEnv)94 public void postRoundProcess(RoundEnvironment roundEnv) throws Exception { 95 Set<Element> newElements = generatesRootInputs.getElementsToWaitFor(roundEnv); 96 if (!processed.isEmpty() ) { 97 checkState( 98 newElements.isEmpty(), 99 "Found extra modules after compilation: %s\n" 100 + "(If you are adding an annotation processor that generates root input for hilt, " 101 + "the annotation must be annotated with @dagger.hilt.GeneratesRootInput.\n)", 102 newElements); 103 } 104 105 if (!newElements.isEmpty()) { 106 // Skip further processing since there's new elements that generate root inputs in this round. 107 return; 108 } 109 110 ImmutableSet<Root> allRoots = 111 AggregatedRootMetadata.from(getElementUtils()).stream() 112 .map(metadata -> Root.create(metadata.rootElement(), getProcessingEnv())) 113 .collect(toImmutableSet()); 114 115 ImmutableSet<Root> processedRoots = 116 ProcessedRootSentinelMetadata.from(getElementUtils()).stream() 117 .flatMap(metadata -> metadata.rootElements().stream()) 118 .map(rootElement -> Root.create(rootElement, getProcessingEnv())) 119 .collect(toImmutableSet()); 120 121 ImmutableSet<Root> rootsToProcess = 122 allRoots.stream() 123 .filter(root -> !processedRoots.contains(root)) 124 .filter(root -> !processed.contains(rootName(root))) 125 .collect(toImmutableSet()); 126 127 if (rootsToProcess.isEmpty()) { 128 // Skip further processing since there's no roots that need processing. 129 return; 130 } 131 132 // TODO(bcorso): Currently, if there's an exception in any of the roots we stop processing 133 // all roots. We should consider if it's worth trying to continue processing for other 134 // roots. At the moment, I think it's rare that if one root failed the others would not. 135 try { 136 validateRoots(allRoots, rootsToProcess); 137 138 boolean isTestEnv = rootsToProcess.stream().anyMatch(Root::isTestRoot); 139 ComponentNames componentNames = 140 isTestEnv && isSharedTestComponentsEnabled(getProcessingEnv()) 141 ? ComponentNames.withRenamingIntoPackage( 142 ClassNames.DEFAULT_ROOT.packageName(), 143 rootsToProcess.stream().map(Root::element).collect(toImmutableList())) 144 : ComponentNames.withoutRenaming(); 145 146 ImmutableSet<ComponentDescriptor> componentDescriptors = 147 defineComponents.getComponentDescriptors(getElementUtils()); 148 ComponentTree tree = ComponentTree.from(componentDescriptors); 149 ComponentDependencies deps = 150 ComponentDependencies.from(componentDescriptors, getElementUtils()); 151 ImmutableList<RootMetadata> rootMetadatas = 152 rootsToProcess.stream() 153 .map(root -> RootMetadata.create(root, tree, deps, getProcessingEnv())) 154 .collect(toImmutableList()); 155 156 for (RootMetadata rootMetadata : rootMetadatas) { 157 if (!rootMetadata.canShareTestComponents()) { 158 generateComponents(rootMetadata, componentNames); 159 } 160 } 161 162 if (isTestEnv) { 163 ImmutableList<RootMetadata> rootsThatCanShareComponents = 164 rootMetadatas.stream() 165 .filter(RootMetadata::canShareTestComponents) 166 .collect(toImmutableList()); 167 generateTestComponentData(rootMetadatas, componentNames); 168 if (deps.hasEarlyEntryPoints() || !rootsThatCanShareComponents.isEmpty()) { 169 Root defaultRoot = Root.createDefaultRoot(getProcessingEnv()); 170 generateComponents( 171 RootMetadata.createForDefaultRoot( 172 defaultRoot, rootsThatCanShareComponents, tree, deps, getProcessingEnv()), 173 componentNames); 174 EarlySingletonComponentCreatorGenerator.generate(getProcessingEnv()); 175 } 176 } 177 } catch (Exception e) { 178 for (Root root : rootsToProcess) { 179 processed.add(rootName(root)); 180 } 181 throw e; 182 } finally { 183 rootsToProcess.forEach(this::setProcessingState); 184 // Calculate the roots processed in this round. We do this in the finally-block rather than in 185 // the try-block because the catch-block can change the processing state. 186 ImmutableSet<Root> rootsProcessedInRound = 187 rootsToProcess.stream() 188 // Only add a sentinel for processed roots. Skip preprocessed roots since those will 189 // will be processed in the next round. 190 .filter(root -> processed.contains(rootName(root))) 191 .collect(toImmutableSet()); 192 for (Root root : rootsProcessedInRound) { 193 new ProcessedRootSentinelGenerator(rootElement(root), getProcessingEnv()).generate(); 194 } 195 } 196 } 197 validateRoots(ImmutableSet<Root> allRoots, ImmutableSet<Root> rootsToProcess)198 private void validateRoots(ImmutableSet<Root> allRoots, ImmutableSet<Root> rootsToProcess) { 199 200 ImmutableSet<TypeElement> rootElementsToProcess = 201 rootsToProcess.stream() 202 .map(Root::element) 203 .sorted(QUALIFIED_NAME_COMPARATOR) 204 .collect(toImmutableSet()); 205 206 ImmutableSet<TypeElement> appRootElementsToProcess = 207 rootsToProcess.stream() 208 .filter(root -> !root.isTestRoot()) 209 .map(Root::element) 210 .sorted(QUALIFIED_NAME_COMPARATOR) 211 .collect(toImmutableSet()); 212 213 // Perform validation between roots in this compilation unit. 214 if (!appRootElementsToProcess.isEmpty()) { 215 ImmutableSet<TypeElement> testRootElementsToProcess = 216 rootsToProcess.stream() 217 .filter(Root::isTestRoot) 218 .map(Root::element) 219 .sorted(QUALIFIED_NAME_COMPARATOR) 220 .collect(toImmutableSet()); 221 222 ProcessorErrors.checkState( 223 testRootElementsToProcess.isEmpty(), 224 "Cannot process test roots and app roots in the same compilation unit:" 225 + "\n\tApp root in this compilation unit: %s" 226 + "\n\tTest roots in this compilation unit: %s", 227 appRootElementsToProcess, 228 testRootElementsToProcess); 229 230 ProcessorErrors.checkState( 231 appRootElementsToProcess.size() == 1, 232 "Cannot process multiple app roots in the same compilation unit: %s", 233 appRootElementsToProcess); 234 } 235 236 // Perform validation across roots previous compilation units. 237 if (!isCrossCompilationRootValidationDisabled(rootElementsToProcess, getProcessingEnv())) { 238 ImmutableSet<TypeElement> processedTestRootElements = 239 allRoots.stream() 240 .filter(Root::isTestRoot) 241 .filter(root -> !rootsToProcess.contains(root)) 242 .map(Root::element) 243 .sorted(QUALIFIED_NAME_COMPARATOR) 244 .collect(toImmutableSet()); 245 246 // TODO(b/185742783): Add an explanation or link to docs to explain why we're forbidding this. 247 ProcessorErrors.checkState( 248 processedTestRootElements.isEmpty(), 249 "Cannot process new roots when there are test roots from a previous compilation unit:" 250 + "\n\tTest roots from previous compilation unit: %s" 251 + "\n\tAll roots from this compilation unit: %s", 252 processedTestRootElements, 253 rootElementsToProcess); 254 255 ImmutableSet<TypeElement> processedAppRootElements = 256 allRoots.stream() 257 .filter(root -> !root.isTestRoot()) 258 .filter(root -> !rootsToProcess.contains(root)) 259 .map(Root::element) 260 .sorted(QUALIFIED_NAME_COMPARATOR) 261 .collect(toImmutableSet()); 262 263 ProcessorErrors.checkState( 264 processedAppRootElements.isEmpty() || appRootElementsToProcess.isEmpty(), 265 "Cannot process app roots in this compilation unit since there are app roots in a " 266 + "previous compilation unit:" 267 + "\n\tApp roots in previous compilation unit: %s" 268 + "\n\tApp roots in this compilation unit: %s", 269 processedAppRootElements, 270 appRootElementsToProcess); 271 } 272 } 273 setProcessingState(Root root)274 private void setProcessingState(Root root) { 275 processed.add(rootName(root)); 276 } 277 rootName(Root root)278 private ClassName rootName(Root root) { 279 return ClassName.get(rootElement(root)); 280 } 281 rootElement(Root root)282 private TypeElement rootElement(Root root) { 283 return root.element(); 284 } 285 generateComponents(RootMetadata rootMetadata, ComponentNames componentNames)286 private void generateComponents(RootMetadata rootMetadata, ComponentNames componentNames) 287 throws IOException { 288 RootGenerator.generate(rootMetadata, componentNames, getProcessingEnv()); 289 } 290 generateTestComponentData( ImmutableList<RootMetadata> rootMetadatas, ComponentNames componentNames)291 private void generateTestComponentData( 292 ImmutableList<RootMetadata> rootMetadatas, ComponentNames componentNames) throws IOException { 293 for (RootMetadata rootMetadata : rootMetadatas) { 294 // TODO(bcorso): Consider moving this check earlier into processEach. 295 TypeElement testElement = rootMetadata.testRootMetadata().testElement(); 296 ProcessorErrors.checkState( 297 testElement.getModifiers().contains(PUBLIC), 298 testElement, 299 "Hilt tests must be public, but found: %s", 300 testElement); 301 new TestComponentDataGenerator(getProcessingEnv(), rootMetadata, componentNames).generate(); 302 } 303 } 304 } 305