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