• 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.aggregateddeps;
18 
19 import static com.google.common.base.Preconditions.checkState;
20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
21 
22 import com.google.auto.value.AutoValue;
23 import com.google.common.collect.ImmutableSet;
24 import com.google.common.collect.ImmutableSetMultimap;
25 import com.squareup.javapoet.ClassName;
26 import dagger.hilt.processor.internal.ClassNames;
27 import dagger.hilt.processor.internal.ComponentDescriptor;
28 import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata;
29 import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata;
30 import javax.lang.model.element.TypeElement;
31 import javax.lang.model.util.Elements;
32 
33 /** Represents information needed to create a component (i.e. modules, entry points, etc) */
34 @AutoValue
35 public abstract class ComponentDependencies {
builder()36   private static Builder builder() {
37     return new AutoValue_ComponentDependencies.Builder();
38   }
39 
40   /** Returns the modules for a component, without any filtering. */
modules()41   public abstract Dependencies modules();
42 
43   /** Returns the entry points associated with the given a component. */
entryPoints()44   public abstract Dependencies entryPoints();
45 
46   /** Returns the component entry point associated with the given a component. */
componentEntryPoints()47   public abstract Dependencies componentEntryPoints();
48 
49   /** Returns the set of early entry points */
earlyEntryPoints()50   public abstract ImmutableSet<ClassName> earlyEntryPoints();
51 
52   /** Returns {@code true} if any entry points are annotated with {@code EarlyEntryPoints}. */
hasEarlyEntryPoints()53   public boolean hasEarlyEntryPoints() {
54     return !earlyEntryPoints().isEmpty();
55   }
56 
57   /**
58    * Returns {@code true} if the test binds or uninstalls test-specific bindings that would prevent
59    * it from sharing components with other test roots.
60    */
includesTestDeps(ClassName root)61   public final boolean includesTestDeps(ClassName root) {
62     return modules().testDeps().keySet().stream().anyMatch((key) -> key.test().equals(root))
63         || modules().uninstalledTestDeps().containsKey(root);
64   }
65 
66   @AutoValue.Builder
67   abstract static class Builder {
modulesBuilder()68     abstract Dependencies.Builder modulesBuilder();
69 
entryPointsBuilder()70     abstract Dependencies.Builder entryPointsBuilder();
71 
componentEntryPointsBuilder()72     abstract Dependencies.Builder componentEntryPointsBuilder();
73 
earlyEntryPointsBuilder()74     abstract ImmutableSet.Builder<ClassName> earlyEntryPointsBuilder();
75 
build()76     abstract ComponentDependencies build();
77   }
78 
79   /** A key used for grouping a test dependency by both its component and test name. */
80   @AutoValue
81   abstract static class TestDepKey {
of(ClassName component, ClassName test)82     static TestDepKey of(ClassName component, ClassName test) {
83       return new AutoValue_ComponentDependencies_TestDepKey(component, test);
84     }
85 
86     /** Returns the name of the component this dependency should be installed in. */
component()87     abstract ClassName component();
88 
89     /** Returns the name of the test that this dependency should be installed in. */
test()90     abstract ClassName test();
91   }
92 
93   /**
94    * Holds a set of component dependencies, e.g. modules or entry points.
95    *
96    * <p>This class handles separating dependencies into global and test dependencies. Global
97    * dependencies are installed with every test, where test dependencies are only installed with the
98    * specified test. The total set of dependencies includes all global + test dependencies.
99    */
100   @AutoValue
101   public abstract static class Dependencies {
builder()102     static Builder builder() {
103       return new AutoValue_ComponentDependencies_Dependencies.Builder();
104     }
105 
106     /** Returns the global deps keyed by component. */
globalDeps()107     abstract ImmutableSetMultimap<ClassName, TypeElement> globalDeps();
108 
109     /** Returns the global test deps keyed by component. */
globalTestDeps()110     abstract ImmutableSetMultimap<ClassName, TypeElement> globalTestDeps();
111 
112     /** Returns the test deps keyed by component and test. */
testDeps()113     abstract ImmutableSetMultimap<TestDepKey, TypeElement> testDeps();
114 
115     /** Returns the uninstalled test deps keyed by test. */
uninstalledTestDeps()116     abstract ImmutableSetMultimap<ClassName, TypeElement> uninstalledTestDeps();
117 
118     /** Returns the global uninstalled test deps. */
globalUninstalledTestDeps()119     abstract ImmutableSet<TypeElement> globalUninstalledTestDeps();
120 
121     /** Returns the dependencies to be installed in the global singleton component. */
getGlobalSingletonDeps()122     ImmutableSet<TypeElement> getGlobalSingletonDeps() {
123       return ImmutableSet.<TypeElement>builder()
124           .addAll(
125               globalDeps().get(ClassNames.SINGLETON_COMPONENT).stream()
126                   .filter(dep -> !globalUninstalledTestDeps().contains(dep))
127                   .collect(toImmutableSet()))
128           .addAll(globalTestDeps().get(ClassNames.SINGLETON_COMPONENT))
129           .build();
130     }
131 
132     /** Returns the dependencies to be installed in the given component for the given root. */
get(ClassName component, ClassName root, boolean isTestRoot)133     public ImmutableSet<TypeElement> get(ClassName component, ClassName root, boolean isTestRoot) {
134       if (!isTestRoot) {
135         return globalDeps().get(component);
136       }
137 
138       ImmutableSet<TypeElement> uninstalledTestDepsForRoot = uninstalledTestDeps().get(root);
139       return ImmutableSet.<TypeElement>builder()
140           .addAll(
141               globalDeps().get(component).stream()
142                   .filter(dep -> !uninstalledTestDepsForRoot.contains(dep))
143                   .filter(dep -> !globalUninstalledTestDeps().contains(dep))
144                   .collect(toImmutableSet()))
145           .addAll(globalTestDeps().get(component))
146           .addAll(testDeps().get(TestDepKey.of(component, root)))
147           .build();
148     }
149 
150     @AutoValue.Builder
151     abstract static class Builder {
globalDepsBuilder()152       abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> globalDepsBuilder();
153 
globalTestDepsBuilder()154       abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> globalTestDepsBuilder();
155 
testDepsBuilder()156       abstract ImmutableSetMultimap.Builder<TestDepKey, TypeElement> testDepsBuilder();
157 
uninstalledTestDepsBuilder()158       abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> uninstalledTestDepsBuilder();
159 
globalUninstalledTestDepsBuilder()160       abstract ImmutableSet.Builder<TypeElement> globalUninstalledTestDepsBuilder();
161 
build()162       abstract Dependencies build();
163     }
164   }
165 
166   /**
167    * Pulls the component dependencies from the {@code packageName}.
168    *
169    * <p>Dependency files are generated by the {@link AggregatedDepsProcessor}, and have the form:
170    *
171    * <pre>{@code
172    * {@literal @}AggregatedDeps(
173    *   components = {
174    *       "foo.FooComponent",
175    *       "bar.BarComponent"
176    *   },
177    *   modules = "baz.BazModule"
178    * )
179    *
180    * }</pre>
181    */
from( ImmutableSet<ComponentDescriptor> descriptors, Elements elements)182   public static ComponentDependencies from(
183       ImmutableSet<ComponentDescriptor> descriptors, Elements elements) {
184     ImmutableSet<ClassName> componentNames =
185         descriptors.stream().map(ComponentDescriptor::component).collect(toImmutableSet());
186     ComponentDependencies.Builder componentDependencies = ComponentDependencies.builder();
187     for (AggregatedDepsMetadata metadata : AggregatedDepsMetadata.from(elements)) {
188       Dependencies.Builder builder = null;
189       switch (metadata.dependencyType()) {
190         case MODULE:
191           builder = componentDependencies.modulesBuilder();
192           break;
193         case ENTRY_POINT:
194           builder = componentDependencies.entryPointsBuilder();
195           break;
196         case COMPONENT_ENTRY_POINT:
197           builder = componentDependencies.componentEntryPointsBuilder();
198           break;
199       }
200       for (TypeElement componentElement : metadata.componentElements()) {
201         ClassName componentName = ClassName.get(componentElement);
202         checkState(
203             componentNames.contains(componentName), "%s is not a valid Component.", componentName);
204         if (metadata.testElement().isPresent()) {
205           // In this case the @InstallIn or @TestInstallIn applies to only the given test root.
206           ClassName test = ClassName.get(metadata.testElement().get());
207           builder.testDepsBuilder().put(TestDepKey.of(componentName, test), metadata.dependency());
208           builder.uninstalledTestDepsBuilder().putAll(test, metadata.replacedDependencies());
209         } else {
210           // In this case the @InstallIn or @TestInstallIn applies to all roots
211           if (!metadata.replacedDependencies().isEmpty()) {
212             // If there are replacedDependencies() it means this is a @TestInstallIn
213             builder.globalTestDepsBuilder().put(componentName, metadata.dependency());
214             builder.globalUninstalledTestDepsBuilder().addAll(metadata.replacedDependencies());
215           } else {
216             builder.globalDepsBuilder().put(componentName, metadata.dependency());
217           }
218         }
219       }
220     }
221 
222     AggregatedUninstallModulesMetadata.from(elements)
223         .forEach(
224             metadata ->
225                 componentDependencies
226                     .modulesBuilder()
227                     .uninstalledTestDepsBuilder()
228                     .putAll(
229                         ClassName.get(metadata.testElement()),
230                         metadata.uninstallModuleElements().stream()
231                             .map(module -> PkgPrivateMetadata.publicModule(module, elements))
232                             .collect(toImmutableSet())));
233 
234     AggregatedEarlyEntryPointMetadata.from(elements).stream()
235         .map(AggregatedEarlyEntryPointMetadata::earlyEntryPoint)
236         .map(entryPoint -> PkgPrivateMetadata.publicEarlyEntryPoint(entryPoint, elements))
237         .map(ClassName::get)
238         .forEach(componentDependencies.earlyEntryPointsBuilder()::add);
239 
240     return componentDependencies.build();
241   }
242 }
243