• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.hilt.processor.internal.HiltCompilerOptions.isCrossCompilationRootValidationDisabled;
21 import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled;
22 import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor;
23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
24 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
25 import static java.util.Arrays.stream;
26 
27 import androidx.room.compiler.processing.XElement;
28 import androidx.room.compiler.processing.XFiler.Mode;
29 import androidx.room.compiler.processing.XProcessingEnv;
30 import androidx.room.compiler.processing.XRoundEnv;
31 import androidx.room.compiler.processing.XTypeElement;
32 import com.google.common.collect.ImmutableSet;
33 import com.squareup.javapoet.ClassName;
34 import dagger.hilt.processor.internal.BadInputException;
35 import dagger.hilt.processor.internal.BaseProcessingStep;
36 import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata;
37 import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata;
38 import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata;
39 import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata;
40 import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs;
41 import dagger.hilt.processor.internal.root.ir.AggregatedDepsIr;
42 import dagger.hilt.processor.internal.root.ir.AggregatedEarlyEntryPointIr;
43 import dagger.hilt.processor.internal.root.ir.AggregatedRootIr;
44 import dagger.hilt.processor.internal.root.ir.AggregatedRootIrValidator;
45 import dagger.hilt.processor.internal.root.ir.AggregatedUninstallModulesIr;
46 import dagger.hilt.processor.internal.root.ir.AliasOfPropagatedDataIr;
47 import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIr;
48 import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIrCreator;
49 import dagger.hilt.processor.internal.root.ir.DefineComponentClassesIr;
50 import dagger.hilt.processor.internal.root.ir.InvalidRootsException;
51 import dagger.hilt.processor.internal.root.ir.ProcessedRootSentinelIr;
52 import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata;
53 import dagger.internal.codegen.xprocessing.XElements;
54 import java.util.Set;
55 
56 /** Processor that outputs dagger components based on transitive build deps. */
57 public final class RootProcessingStep extends BaseProcessingStep {
58 
59   private boolean processed;
60   // TODO(b/297889547) do not run preProcess and postProcess if supported annotation isn't present
61   // in the environment.
62   private boolean hasElementsToProcess = false;
63   private GeneratesRootInputs generatesRootInputs;
64 
RootProcessingStep(XProcessingEnv env)65   public RootProcessingStep(XProcessingEnv env) {
66     super(env);
67     generatesRootInputs = new GeneratesRootInputs(processingEnv());
68   }
69 
getMode()70   private Mode getMode() {
71     return useAggregatingRootProcessor(processingEnv()) ? Mode.Aggregating : Mode.Isolating;
72   }
73 
74   @Override
annotationClassNames()75   protected ImmutableSet<ClassName> annotationClassNames() {
76     return stream(RootType.values()).map(RootType::className).collect(toImmutableSet());
77   }
78 
79   @Override
processEach(ClassName annotation, XElement element)80   public void processEach(ClassName annotation, XElement element) throws Exception {
81     hasElementsToProcess = true;
82     XTypeElement rootElement = XElements.asTypeElement(element);
83     // TODO(bcorso): Move this logic into a separate isolating processor to avoid regenerating it
84     // for unrelated changes in Gradle.
85     RootType rootType = RootType.of(rootElement);
86     if (rootType.isTestRoot()) {
87       TestRootMetadata testRootMetadata = TestRootMetadata.of(processingEnv(), rootElement);
88       if (testRootMetadata.skipTestInjectionAnnotation().isEmpty()) {
89         new TestInjectorGenerator(processingEnv(), testRootMetadata).generate();
90       }
91     }
92 
93     XTypeElement originatingRootElement =
94         Root.create(rootElement, processingEnv()).originatingRootElement();
95     new AggregatedRootGenerator(
96             rootElement, originatingRootElement, processingEnv().requireTypeElement(annotation))
97         .generate();
98   }
99 
100   @Override
postProcess(XProcessingEnv env, XRoundEnv roundEnv)101   protected void postProcess(XProcessingEnv env, XRoundEnv roundEnv) throws Exception {
102     if (!hasElementsToProcess) {
103       return;
104     }
105     if (!useAggregatingRootProcessor(processingEnv())) {
106       return;
107     }
108     ImmutableSet<XElement> newElements =
109         generatesRootInputs.getElementsToWaitFor(roundEnv).stream().collect(toImmutableSet());
110     if (processed) {
111       checkState(
112           newElements.isEmpty(),
113           "Found extra modules after compilation: %s\n"
114               + "(If you are adding an annotation processor that generates root input for hilt, "
115               + "the annotation must be annotated with @dagger.hilt.GeneratesRootInput.\n)",
116           newElements.stream().map(XElements::toStableString).collect(toImmutableList()));
117     } else if (newElements.isEmpty()) {
118       processed = true;
119 
120       ImmutableSet<AggregatedRootIr> rootsToProcess = rootsToProcess();
121       if (rootsToProcess.isEmpty()) {
122         return;
123       }
124       // Generate an @ComponentTreeDeps for each unique component tree.
125       ComponentTreeDepsGenerator componentTreeDepsGenerator =
126           new ComponentTreeDepsGenerator(processingEnv(), getMode());
127       for (ComponentTreeDepsMetadata metadata : componentTreeDepsMetadatas(rootsToProcess)) {
128         componentTreeDepsGenerator.generate(metadata);
129       }
130 
131       // Generate a sentinel for all processed roots.
132       for (AggregatedRootIr ir : rootsToProcess) {
133         XTypeElement rootElement = processingEnv().requireTypeElement(ir.getRoot().canonicalName());
134         new ProcessedRootSentinelGenerator(rootElement, getMode()).generate();
135       }
136     }
137   }
138 
rootsToProcess()139   private ImmutableSet<AggregatedRootIr> rootsToProcess() {
140     ImmutableSet<ProcessedRootSentinelIr> processedRoots =
141         ProcessedRootSentinelMetadata.from(processingEnv()).stream()
142             .map(ProcessedRootSentinelMetadata::toIr)
143             .collect(toImmutableSet());
144     ImmutableSet<AggregatedRootIr> aggregatedRoots =
145         AggregatedRootMetadata.from(processingEnv()).stream()
146             .map(AggregatedRootMetadata::toIr)
147             .collect(toImmutableSet());
148 
149     boolean isCrossCompilationRootValidationDisabled =
150         isCrossCompilationRootValidationDisabled(
151             aggregatedRoots.stream()
152                 .map(ir -> processingEnv().requireTypeElement(ir.getRoot().canonicalName()))
153                 .collect(toImmutableSet()),
154             processingEnv());
155     try {
156       return ImmutableSet.copyOf(
157           AggregatedRootIrValidator.rootsToProcess(
158               isCrossCompilationRootValidationDisabled, processedRoots, aggregatedRoots));
159     } catch (InvalidRootsException ex) {
160       throw new BadInputException(ex.getMessage());
161     }
162   }
163 
componentTreeDepsMetadatas( ImmutableSet<AggregatedRootIr> aggregatedRoots)164   private ImmutableSet<ComponentTreeDepsMetadata> componentTreeDepsMetadatas(
165       ImmutableSet<AggregatedRootIr> aggregatedRoots) {
166     ImmutableSet<DefineComponentClassesIr> defineComponentDeps =
167         DefineComponentClassesMetadata.from(processingEnv()).stream()
168             .map(DefineComponentClassesMetadata::toIr)
169             .collect(toImmutableSet());
170     ImmutableSet<AliasOfPropagatedDataIr> aliasOfDeps =
171         AliasOfPropagatedDataMetadata.from(processingEnv()).stream()
172             .map(AliasOfPropagatedDataMetadata::toIr)
173             .collect(toImmutableSet());
174     ImmutableSet<AggregatedDepsIr> aggregatedDeps =
175         AggregatedDepsMetadata.from(processingEnv()).stream()
176             .map(AggregatedDepsMetadata::toIr)
177             .collect(toImmutableSet());
178     ImmutableSet<AggregatedUninstallModulesIr> aggregatedUninstallModulesDeps =
179         AggregatedUninstallModulesMetadata.from(processingEnv()).stream()
180             .map(AggregatedUninstallModulesMetadata::toIr)
181             .collect(toImmutableSet());
182     ImmutableSet<AggregatedEarlyEntryPointIr> aggregatedEarlyEntryPointDeps =
183         AggregatedEarlyEntryPointMetadata.from(processingEnv()).stream()
184             .map(AggregatedEarlyEntryPointMetadata::toIr)
185             .collect(toImmutableSet());
186 
187     // We should be guaranteed that there are no mixed roots, so check if this is prod or test.
188     boolean isTest = aggregatedRoots.stream().anyMatch(AggregatedRootIr::isTestRoot);
189     Set<ComponentTreeDepsIr> componentTreeDeps =
190         ComponentTreeDepsIrCreator.components(
191             isTest,
192             isSharedTestComponentsEnabled(processingEnv()),
193             aggregatedRoots,
194             defineComponentDeps,
195             aliasOfDeps,
196             aggregatedDeps,
197             aggregatedUninstallModulesDeps,
198             aggregatedEarlyEntryPointDeps);
199     return componentTreeDeps.stream()
200         .map(it -> ComponentTreeDepsMetadata.from(it, processingEnv()))
201         .collect(toImmutableSet());
202   }
203 }
204