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.internal.codegen.extension.DaggerStreams.toImmutableList; 21 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 22 import static javax.lang.model.element.Modifier.PUBLIC; 23 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.AGGREGATING; 24 25 import com.google.auto.common.MoreElements; 26 import com.google.auto.service.AutoService; 27 import com.google.common.collect.ImmutableList; 28 import com.google.common.collect.ImmutableSet; 29 import com.squareup.javapoet.ClassName; 30 import dagger.hilt.processor.internal.BaseProcessor; 31 import dagger.hilt.processor.internal.ComponentTree; 32 import dagger.hilt.processor.internal.ProcessorErrors; 33 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; 34 import dagger.hilt.processor.internal.definecomponent.DefineComponents; 35 import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs; 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Set; 42 import javax.annotation.processing.ProcessingEnvironment; 43 import javax.annotation.processing.Processor; 44 import javax.annotation.processing.RoundEnvironment; 45 import javax.lang.model.element.Element; 46 import javax.lang.model.element.TypeElement; 47 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; 48 49 /** Processor that outputs dagger components based on transitive build deps. */ 50 @IncrementalAnnotationProcessor(AGGREGATING) 51 @AutoService(Processor.class) 52 public final class RootProcessor extends BaseProcessor { 53 private final List<ClassName> rootNames = new ArrayList<>(); 54 private final Set<ClassName> processed = new HashSet<>(); 55 private boolean isTestEnv; 56 // TODO(bcorso): Consider using a Dagger component to create/scope these objects 57 private final DefineComponents defineComponents = DefineComponents.create(); 58 private GeneratesRootInputs generatesRootInputs; 59 60 @Override init(ProcessingEnvironment processingEnvironment)61 public synchronized void init(ProcessingEnvironment processingEnvironment) { 62 super.init(processingEnvironment); 63 generatesRootInputs = new GeneratesRootInputs(processingEnvironment); 64 } 65 66 @Override getSupportedAnnotationTypes()67 public ImmutableSet<String> getSupportedAnnotationTypes() { 68 return ImmutableSet.<String>builder() 69 .addAll( 70 Arrays.stream(RootType.values()) 71 .map(rootType -> rootType.className().toString()) 72 .collect(toImmutableSet())) 73 .build(); 74 } 75 76 @Override processEach(TypeElement annotation, Element element)77 public void processEach(TypeElement annotation, Element element) throws Exception { 78 TypeElement rootElement = MoreElements.asType(element); 79 boolean isTestRoot = RootType.of(getProcessingEnv(), rootElement).isTestRoot(); 80 checkState( 81 rootNames.isEmpty() || isTestEnv == isTestRoot, 82 "Cannot mix test roots with non-test roots:" 83 + "\n\tNon-Test Roots: %s" 84 + "\n\tTest Roots: %s", 85 isTestRoot ? rootNames : rootElement, 86 isTestRoot ? rootElement : rootNames); 87 isTestEnv = isTestRoot; 88 89 rootNames.add(ClassName.get(rootElement)); 90 if (isTestEnv) { 91 new TestInjectorGenerator( 92 getProcessingEnv(), 93 TestRootMetadata.of(getProcessingEnv(), rootElement)).generate(); 94 } else { 95 ProcessorErrors.checkState( 96 rootNames.size() <= 1, element, "More than one root found: %s", rootNames); 97 } 98 } 99 100 @Override postRoundProcess(RoundEnvironment roundEnv)101 public void postRoundProcess(RoundEnvironment roundEnv) throws Exception { 102 Set<Element> newElements = generatesRootInputs.getElementsToWaitFor(roundEnv); 103 if (!processed.isEmpty() ) { 104 checkState( 105 newElements.isEmpty(), 106 "Found extra modules after compilation: %s\n" 107 + "(If you are adding an annotation processor that generates root input for hilt, " 108 + "the annotation must be annotated with @dagger.hilt.GeneratesRootInput.\n)", 109 newElements); 110 } 111 112 if (!newElements.isEmpty()) { 113 // Skip further processing since there's new elements that generate root inputs in this round. 114 return; 115 } 116 117 ImmutableList<Root> rootsToProcess = 118 rootNames.stream() 119 .filter(rootName -> !processed.contains(rootName)) 120 // We create a new root element each round to avoid the jdk8 bug where 121 // TypeElement.equals does not work for elements across processing rounds. 122 .map(rootName -> getElementUtils().getTypeElement(rootName.toString())) 123 .map(rootElement -> Root.create(rootElement, getProcessingEnv())) 124 .collect(toImmutableList()); 125 126 if (rootsToProcess.isEmpty()) { 127 // Skip further processing since there's no roots that need processing. 128 return; 129 } 130 131 // TODO(bcorso): Currently, if there's an exception in any of the roots we stop processing 132 // all roots. We should consider if it's worth trying to continue processing for other 133 // roots. At the moment, I think it's rare that if one root failed the others would not. 134 try { 135 ComponentTree tree = defineComponents.getComponentTree(getElementUtils()); 136 ComponentDependencies deps = ComponentDependencies.from( 137 tree.getComponentDescriptors(), getElementUtils()); 138 ImmutableList<RootMetadata> rootMetadatas = 139 rootsToProcess.stream() 140 .map(root -> RootMetadata.create(root, tree, deps, getProcessingEnv())) 141 .collect(toImmutableList()); 142 143 for (RootMetadata rootMetadata : rootMetadatas) { 144 setProcessingState(rootMetadata.root()); 145 generateComponents(rootMetadata); 146 } 147 148 if (isTestEnv) { 149 generateTestComponentData(rootMetadatas); 150 } 151 } catch (Exception e) { 152 for (Root root : rootsToProcess) { 153 processed.add(root.classname()); 154 } 155 throw e; 156 } 157 } 158 setProcessingState(Root root)159 private void setProcessingState(Root root) { 160 processed.add(root.classname()); 161 } 162 generateComponents(RootMetadata rootMetadata)163 private void generateComponents(RootMetadata rootMetadata) throws IOException { 164 RootGenerator.generate(rootMetadata, getProcessingEnv()); 165 } 166 generateTestComponentData(ImmutableList<RootMetadata> rootMetadatas)167 private void generateTestComponentData(ImmutableList<RootMetadata> rootMetadatas) 168 throws IOException { 169 for (RootMetadata rootMetadata : rootMetadatas) { 170 // TODO(bcorso): Consider moving this check earlier into processEach. 171 TypeElement testElement = rootMetadata.testRootMetadata().testElement(); 172 ProcessorErrors.checkState( 173 testElement.getModifiers().contains(PUBLIC), 174 testElement, 175 "Hilt tests must be public, but found: %s", 176 testElement); 177 new TestComponentDataGenerator(getProcessingEnv(), rootMetadata).generate(); 178 } 179 new TestComponentDataSupplierGenerator(getProcessingEnv(), rootMetadatas).generate(); 180 } 181 } 182