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