1 /* 2 * Copyright (C) 2021 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.auto.common.MoreElements.asType; 20 import static com.google.common.collect.Iterables.getOnlyElement; 21 import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor; 22 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 24 import static javax.lang.model.element.Modifier.PUBLIC; 25 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; 26 27 import com.google.auto.service.AutoService; 28 import com.google.common.collect.ImmutableList; 29 import com.google.common.collect.ImmutableSet; 30 import com.squareup.javapoet.ClassName; 31 import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata; 32 import dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator; 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.Processors; 39 import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata; 40 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; 41 import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata; 42 import dagger.hilt.processor.internal.aliasof.AliasOfs; 43 import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata; 44 import dagger.hilt.processor.internal.definecomponent.DefineComponents; 45 import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata; 46 import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata; 47 import java.io.IOException; 48 import java.util.HashSet; 49 import java.util.Set; 50 import javax.annotation.processing.Processor; 51 import javax.annotation.processing.RoundEnvironment; 52 import javax.lang.model.element.Element; 53 import javax.lang.model.element.TypeElement; 54 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; 55 56 /** Processor that outputs dagger components based on transitive build deps. */ 57 @IncrementalAnnotationProcessor(ISOLATING) 58 @AutoService(Processor.class) 59 public final class ComponentTreeDepsProcessor extends BaseProcessor { 60 private final Set<ClassName> componentTreeDepNames = new HashSet<>(); 61 private final Set<ClassName> processed = new HashSet<>(); 62 private final DefineComponents defineComponents = DefineComponents.create(); 63 64 @Override getSupportedAnnotationTypes()65 public ImmutableSet<String> getSupportedAnnotationTypes() { 66 return ImmutableSet.of(ClassNames.COMPONENT_TREE_DEPS.toString()); 67 } 68 69 @Override processEach(TypeElement annotation, Element element)70 public void processEach(TypeElement annotation, Element element) { 71 componentTreeDepNames.add(ClassName.get(asType(element))); 72 } 73 74 @Override postRoundProcess(RoundEnvironment roundEnv)75 public void postRoundProcess(RoundEnvironment roundEnv) throws Exception { 76 ImmutableSet<ComponentTreeDepsMetadata> componentTreeDepsToProcess = 77 componentTreeDepNames.stream() 78 .filter(className -> !processed.contains(className)) 79 .map(className -> getElementUtils().getTypeElement(className.canonicalName())) 80 .map(element -> ComponentTreeDepsMetadata.from(element, getElementUtils())) 81 .collect(toImmutableSet()); 82 83 for (ComponentTreeDepsMetadata metadata : componentTreeDepsToProcess) { 84 processComponentTreeDeps(metadata); 85 } 86 } 87 processComponentTreeDeps(ComponentTreeDepsMetadata metadata)88 private void processComponentTreeDeps(ComponentTreeDepsMetadata metadata) throws IOException { 89 TypeElement metadataElement = getElementUtils().getTypeElement(metadata.name().canonicalName()); 90 try { 91 // We choose a name for the generated components/wrapper based off of the originating element 92 // annotated with @ComponentTreeDeps. This is close to but isn't necessarily a "real" name of 93 // a root, since with shared test components, even for single roots, the component tree deps 94 // will be moved to a shared package with a deduped name. 95 ClassName renamedRoot = Processors.removeNameSuffix(metadataElement, "_ComponentTreeDeps"); 96 ComponentNames componentNames = ComponentNames.withRenaming(rootName -> renamedRoot); 97 98 boolean isDefaultRoot = ClassNames.DEFAULT_ROOT.equals(renamedRoot); 99 ImmutableSet<Root> roots = 100 AggregatedRootMetadata.from(metadata.aggregatedRootDeps(), processingEnv).stream() 101 .map(AggregatedRootMetadata::rootElement) 102 .map(rootElement -> Root.create(rootElement, getProcessingEnv())) 103 .collect(toImmutableSet()); 104 105 // TODO(bcorso): For legacy reasons, a lot of the generating code requires a "root" as input 106 // since we used to assume 1 root per component tree. Now that each ComponentTreeDeps may 107 // represent multiple roots, we should refactor this logic. 108 Root root = 109 isDefaultRoot 110 ? Root.createDefaultRoot(getProcessingEnv()) 111 // Non-default roots should only ever be associated with one root element 112 : getOnlyElement(roots); 113 114 ImmutableSet<ComponentDescriptor> componentDescriptors = 115 defineComponents.getComponentDescriptors( 116 DefineComponentClassesMetadata.from( 117 metadata.defineComponentDeps(), getElementUtils())); 118 ComponentTree tree = ComponentTree.from(componentDescriptors); 119 ComponentDependencies deps = 120 ComponentDependencies.from( 121 componentDescriptors, 122 AggregatedDepsMetadata.from(metadata.aggregatedDeps(), getElementUtils()), 123 AggregatedUninstallModulesMetadata.from( 124 metadata.aggregatedUninstallModulesDeps(), getElementUtils()), 125 AggregatedEarlyEntryPointMetadata.from( 126 metadata.aggregatedEarlyEntryPointDeps(), getElementUtils()), 127 getElementUtils()); 128 AliasOfs aliasOfs = 129 AliasOfs.create( 130 AliasOfPropagatedDataMetadata.from(metadata.aliasOfDeps(), getElementUtils()), 131 componentDescriptors); 132 RootMetadata rootMetadata = 133 RootMetadata.create(root, tree, deps, aliasOfs, getProcessingEnv()); 134 135 generateComponents(metadata, rootMetadata, componentNames); 136 137 // Generate a creator for the early entry point if there is a default component available 138 // and there are early entry points. 139 if (isDefaultRoot && !metadata.aggregatedEarlyEntryPointDeps().isEmpty()) { 140 EarlySingletonComponentCreatorGenerator.generate(getProcessingEnv()); 141 } 142 143 if (root.isTestRoot()) { 144 // Generate test related classes for each test root that uses this component. 145 ImmutableList<RootMetadata> rootMetadatas = 146 roots.stream() 147 .map(test -> RootMetadata.create(test, tree, deps, aliasOfs, getProcessingEnv())) 148 .collect(toImmutableList()); 149 generateTestComponentData(metadataElement, rootMetadatas, componentNames); 150 } else { 151 generateApplication(root.element()); 152 } 153 154 setProcessingState(metadata, root); 155 } catch (Exception e) { 156 processed.add(metadata.name()); 157 throw e; 158 } 159 } 160 setProcessingState(ComponentTreeDepsMetadata metadata, Root root)161 private void setProcessingState(ComponentTreeDepsMetadata metadata, Root root) { 162 processed.add(metadata.name()); 163 } 164 generateComponents( ComponentTreeDepsMetadata metadata, RootMetadata rootMetadata, ComponentNames componentNames)165 private void generateComponents( 166 ComponentTreeDepsMetadata metadata, RootMetadata rootMetadata, ComponentNames componentNames) 167 throws IOException { 168 RootGenerator.generate(metadata, rootMetadata, componentNames, getProcessingEnv()); 169 } 170 generateTestComponentData( TypeElement metadataElement, ImmutableList<RootMetadata> rootMetadatas, ComponentNames componentNames)171 private void generateTestComponentData( 172 TypeElement metadataElement, 173 ImmutableList<RootMetadata> rootMetadatas, 174 ComponentNames componentNames) 175 throws IOException { 176 for (RootMetadata rootMetadata : rootMetadatas) { 177 // TODO(bcorso): Consider moving this check earlier into processEach. 178 TypeElement testElement = rootMetadata.testRootMetadata().testElement(); 179 ProcessorErrors.checkState( 180 testElement.getModifiers().contains(PUBLIC), 181 testElement, 182 "Hilt tests must be public, but found: %s", 183 testElement); 184 new TestComponentDataGenerator( 185 getProcessingEnv(), metadataElement, rootMetadata, componentNames) 186 .generate(); 187 } 188 } 189 generateApplication(TypeElement rootElement)190 private void generateApplication(TypeElement rootElement) throws IOException { 191 // The generated application references the generated component so they must be generated 192 // in the same build unit. Thus, we only generate the application here if we're using the 193 // Hilt Gradle plugin's aggregating task. If we're using the aggregating processor, we need 194 // to generate the application within AndroidEntryPointProcessor instead. 195 if (!useAggregatingRootProcessor(getProcessingEnv())) { 196 AndroidEntryPointMetadata metadata = 197 AndroidEntryPointMetadata.of(getProcessingEnv(), rootElement); 198 new ApplicationGenerator( 199 getProcessingEnv(), 200 metadata) 201 .generate(); 202 } 203 } 204 } 205