• 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.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