• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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