diff options
Diffstat (limited to 'java/dagger/hilt/processor/internal/aggregateddeps')
6 files changed, 349 insertions, 319 deletions
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java index 84fb5a1ee..bcc2dfe04 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java @@ -19,6 +19,8 @@ package dagger.hilt.processor.internal.aggregateddeps; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; import dagger.hilt.processor.internal.Processors; import java.io.IOException; import java.util.Optional; @@ -57,8 +59,20 @@ final class AggregatedDepsGenerator { } void generate() throws IOException { - Processors.generateAggregatingClass( - AGGREGATING_PACKAGE, aggregatedDepsAnnotation(), dependency, getClass(), processingEnv); + ClassName name = + ClassName.get( + AGGREGATING_PACKAGE, Processors.getFullEnclosedName(dependency) + "ModuleDeps"); + TypeSpec.Builder generator = + TypeSpec.classBuilder(name.simpleName()) + .addOriginatingElement(dependency) + .addAnnotation(aggregatedDepsAnnotation()) + .addJavadoc("Generated class to pass information through multiple javac runs.\n"); + + Processors.addGeneratedAnnotation(generator, processingEnv, getClass()); + + JavaFile.builder(name.packageName(), generator.build()) + .build() + .writeTo(processingEnv.getFiler()); } private AnnotationSpec aggregatedDepsAnnotation() { diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java deleted file mode 100644 index 09e1651fe..000000000 --- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2021 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.hilt.processor.internal.aggregateddeps; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; - -import com.google.auto.value.AutoValue; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import dagger.hilt.processor.internal.AggregatedElements; -import dagger.hilt.processor.internal.AnnotationValues; -import dagger.hilt.processor.internal.ClassNames; -import dagger.hilt.processor.internal.Processors; -import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; - -/** - * A class that represents the values stored in an {@link - * dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps} annotation. - */ -@AutoValue -abstract class AggregatedDepsMetadata { - private static final String AGGREGATED_DEPS_PACKAGE = "hilt_aggregated_deps"; - - enum DependencyType { - MODULE, - ENTRY_POINT, - COMPONENT_ENTRY_POINT - } - - abstract Optional<TypeElement> testElement(); - - abstract ImmutableSet<TypeElement> componentElements(); - - abstract DependencyType dependencyType(); - - abstract TypeElement dependency(); - - abstract ImmutableSet<TypeElement> replacedDependencies(); - - /** Returns all aggregated deps in the aggregating package. */ - public static ImmutableSet<AggregatedDepsMetadata> from(Elements elements) { - return AggregatedElements.from(AGGREGATED_DEPS_PACKAGE, ClassNames.AGGREGATED_DEPS, elements) - .stream() - .map(aggregatedElement -> create(aggregatedElement, elements)) - .collect(toImmutableSet()); - } - - private static AggregatedDepsMetadata create(TypeElement element, Elements elements) { - AnnotationMirror annotationMirror = - Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_DEPS); - - ImmutableMap<String, AnnotationValue> values = - Processors.getAnnotationValues(elements, annotationMirror); - - return new AutoValue_AggregatedDepsMetadata( - getTestElement(values.get("test"), elements), - getComponents(values.get("components"), elements), - getDependencyType( - values.get("modules"), - values.get("entryPoints"), - values.get("componentEntryPoints")), - getDependency( - values.get("modules"), - values.get("entryPoints"), - values.get("componentEntryPoints"), - elements), - getReplacedDependencies(values.get("replaces"), elements)); - } - - private static Optional<TypeElement> getTestElement( - AnnotationValue testValue, Elements elements) { - checkNotNull(testValue); - String test = AnnotationValues.getString(testValue); - return test.isEmpty() ? Optional.empty() : Optional.of(elements.getTypeElement(test)); - } - - private static ImmutableSet<TypeElement> getComponents( - AnnotationValue componentsValue, Elements elements) { - checkNotNull(componentsValue); - ImmutableSet<TypeElement> componentNames = - AnnotationValues.getAnnotationValues(componentsValue).stream() - .map(AnnotationValues::getString) - .map( - // This is a temporary hack to map the old ApplicationComponent to the new - // SingletonComponent. Technically, this is only needed for backwards compatibility - // with libraries using the old processor since new processors should convert to the - // new SingletonComponent when generating the metadata class. - componentName -> - componentName.contentEquals( - "dagger.hilt.android.components.ApplicationComponent") - ? ClassNames.SINGLETON_COMPONENT.canonicalName() - : componentName) - .map(elements::getTypeElement) - .collect(toImmutableSet()); - checkState(!componentNames.isEmpty()); - return componentNames; - } - - private static DependencyType getDependencyType( - AnnotationValue modulesValue, - AnnotationValue entryPointsValue, - AnnotationValue componentEntryPointsValue) { - checkNotNull(modulesValue); - checkNotNull(entryPointsValue); - checkNotNull(componentEntryPointsValue); - - ImmutableSet.Builder<DependencyType> dependencyTypes = ImmutableSet.builder(); - if (!AnnotationValues.getAnnotationValues(modulesValue).isEmpty()) { - dependencyTypes.add(DependencyType.MODULE); - } - if (!AnnotationValues.getAnnotationValues(entryPointsValue).isEmpty()) { - dependencyTypes.add(DependencyType.ENTRY_POINT); - } - if (!AnnotationValues.getAnnotationValues(componentEntryPointsValue).isEmpty()) { - dependencyTypes.add(DependencyType.COMPONENT_ENTRY_POINT); - } - return getOnlyElement(dependencyTypes.build()); - } - - private static TypeElement getDependency( - AnnotationValue modulesValue, - AnnotationValue entryPointsValue, - AnnotationValue componentEntryPointsValue, - Elements elements) { - checkNotNull(modulesValue); - checkNotNull(entryPointsValue); - checkNotNull(componentEntryPointsValue); - - return elements.getTypeElement( - AnnotationValues.getString( - getOnlyElement( - ImmutableSet.<AnnotationValue>builder() - .addAll(AnnotationValues.getAnnotationValues(modulesValue)) - .addAll(AnnotationValues.getAnnotationValues(entryPointsValue)) - .addAll(AnnotationValues.getAnnotationValues(componentEntryPointsValue)) - .build()))); - } - - private static ImmutableSet<TypeElement> getReplacedDependencies( - AnnotationValue replacedDependenciesValue, Elements elements) { - // Allow null values to support libraries using a Hilt version before @TestInstallIn was added - return replacedDependenciesValue == null - ? ImmutableSet.of() - : AnnotationValues.getAnnotationValues(replacedDependenciesValue).stream() - .map(AnnotationValues::getString) - .map(elements::getTypeElement) - .map(replacedDep -> getPublicDependency(replacedDep, elements)) - .collect(toImmutableSet()); - } - - /** Returns the public Hilt wrapper module, or the module itself if its already public. */ - private static TypeElement getPublicDependency(TypeElement dependency, Elements elements) { - return PkgPrivateMetadata.of(elements, dependency, ClassNames.MODULE) - .map(metadata -> elements.getTypeElement(metadata.generatedClassName().toString())) - .orElse(dependency); - } -} diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java index 151401e60..58c938f88 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java @@ -20,7 +20,7 @@ import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.MoreElements.asType; import static com.google.auto.common.MoreElements.getPackage; import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.hilt.processor.internal.HiltCompilerOptions.isModuleInstallInCheckDisabled; +import static dagger.hilt.android.processor.internal.androidentrypoint.HiltCompilerOptions.BooleanOption.DISABLE_MODULES_HAVE_INSTALL_IN_CHECK; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static javax.lang.model.element.ElementKind.CLASS; @@ -37,8 +37,10 @@ import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.BaseProcessor; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Components; +import dagger.hilt.processor.internal.KotlinMetadataUtils; import dagger.hilt.processor.internal.ProcessorErrors; import dagger.hilt.processor.internal.Processors; +import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -63,7 +65,6 @@ public final class AggregatedDepsProcessor extends BaseProcessor { private static final ImmutableSet<ClassName> ENTRY_POINT_ANNOTATIONS = ImmutableSet.of( ClassNames.ENTRY_POINT, - ClassNames.EARLY_ENTRY_POINT, ClassNames.GENERATED_ENTRY_POINT, ClassNames.COMPONENT_ENTRY_POINT); @@ -165,10 +166,7 @@ public final class AggregatedDepsProcessor extends BaseProcessor { // Check that if Dagger needs an instance of the module, Hilt can provide it automatically by // calling a visible empty constructor. ProcessorErrors.checkState( - // Skip ApplicationContextModule, since Hilt manages this module internally. - ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module)) - || !Processors.requiresModuleInstance(getElementUtils(), module) - || hasVisibleEmptyConstructor(module), + !daggerRequiresModuleInstance(module) || hasVisibleEmptyConstructor(module), module, "Modules that need to be instantiated by Hilt must have a visible, empty constructor."); @@ -188,14 +186,13 @@ public final class AggregatedDepsProcessor extends BaseProcessor { ImmutableList<TypeElement> replacedModules = ImmutableList.of(); if (Processors.hasAnnotation(module, ClassNames.TEST_INSTALL_IN)) { - Optional<TypeElement> originatingTestElement = - Processors.getOriginatingTestElement(module, getElementUtils()); + Optional<TypeElement> originatingTestElement = getOriginatingTestElement(module); ProcessorErrors.checkState( !originatingTestElement.isPresent(), // TODO(b/152801981): this should really error on the annotation value module, "@TestInstallIn modules cannot be nested in (or originate from) a " - + "@HiltAndroidTest-annotated class: %s", + + "@HiltAndroidTest-annotated class: %s", originatingTestElement .map(testElement -> testElement.getQualifiedName().toString()) .orElse("")); @@ -265,10 +262,7 @@ public final class AggregatedDepsProcessor extends BaseProcessor { // Prevent users from uninstalling test-specific @InstallIn modules. ImmutableList<TypeElement> replacedTestSpecificInstallIn = replacedModules.stream() - .filter( - replacedModule -> - Processors.getOriginatingTestElement(replacedModule, getElementUtils()) - .isPresent()) + .filter(replacedModule -> getOriginatingTestElement(replacedModule).isPresent()) .collect(toImmutableList()); ProcessorErrors.checkState( @@ -310,28 +304,6 @@ public final class AggregatedDepsProcessor extends BaseProcessor { element); TypeElement entryPoint = asType(element); - if (entryPointAnnotation.equals(ClassNames.EARLY_ENTRY_POINT)) { - ImmutableSet<ClassName> components = Components.getComponents(getElementUtils(), element); - ProcessorErrors.checkState( - components.equals(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)), - element, - "@EarlyEntryPoint can only be installed into the SingletonComponent. Found: %s", - components); - - Optional<TypeElement> optionalTestElement = - Processors.getOriginatingTestElement(element, getElementUtils()); - ProcessorErrors.checkState( - !optionalTestElement.isPresent(), - element, - "@EarlyEntryPoint-annotated entry point, %s, cannot be nested in (or originate from) " - + "a @HiltAndroidTest-annotated class, %s. This requirement is to avoid confusion " - + "with other, test-specific entry points.", - asType(element).getQualifiedName().toString(), - optionalTestElement - .map(testElement -> testElement.getQualifiedName().toString()) - .orElse("")); - } - generateAggregatedDeps( entryPointAnnotation.equals(ClassNames.COMPONENT_ENTRY_POINT) ? "componentEntryPoints" @@ -361,8 +333,7 @@ public final class AggregatedDepsProcessor extends BaseProcessor { .generate(); } } else { - Optional<ClassName> testName = - Processors.getOriginatingTestElement(element, getElementUtils()).map(ClassName::get); + Optional<ClassName> testName = getOriginatingTestElement(element).map(ClassName::get); new AggregatedDepsGenerator( key, element, testName, components, replacedModules, getProcessingEnv()) .generate(); @@ -391,6 +362,25 @@ public final class AggregatedDepsProcessor extends BaseProcessor { return Optional.of(getOnlyElement(usedAnnotations)); } + private Optional<TypeElement> getOriginatingTestElement(Element element) { + TypeElement topLevelType = getOriginatingTopLevelType(element); + return Processors.hasAnnotation(topLevelType, ClassNames.HILT_ANDROID_TEST) + ? Optional.of(asType(topLevelType)) + : Optional.empty(); + } + + private TypeElement getOriginatingTopLevelType(Element element) { + TypeElement topLevelType = Processors.getTopLevelType(element); + if (Processors.hasAnnotation(topLevelType, ClassNames.ORIGINATING_ELEMENT)) { + return getOriginatingTopLevelType( + Processors.getAnnotationClassValue( + getElementUtils(), + Processors.getAnnotationMirror(topLevelType, ClassNames.ORIGINATING_ELEMENT), + "topLevelClass")); + } + return topLevelType; + } + private static boolean isValidKind(Element element) { // don't go down the rabbit hole of analyzing undefined types. N.B. we don't issue // an error here because javac already has and we don't want to spam the user. @@ -398,7 +388,7 @@ public final class AggregatedDepsProcessor extends BaseProcessor { } private boolean installInCheckDisabled(Element element) { - return isModuleInstallInCheckDisabled(getProcessingEnv()) + return DISABLE_MODULES_HAVE_INSTALL_IN_CHECK.get(getProcessingEnv()) || Processors.hasAnnotation(element, ClassNames.DISABLE_INSTALL_IN_CHECK); } @@ -445,6 +435,27 @@ public final class AggregatedDepsProcessor extends BaseProcessor { || name.contentEquals("javax.annotation.processing.Generated"); } + private static boolean daggerRequiresModuleInstance(TypeElement module) { + return !module.getModifiers().contains(ABSTRACT) + && !hasOnlyStaticProvides(module) + // Skip ApplicationContextModule, since Hilt manages this module internally. + && !ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module)) + // Skip Kotlin object modules since all their provision methods are static + && !isKotlinObject(module); + } + + private static boolean isKotlinObject(TypeElement type) { + KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil(); + return metadataUtil.isObjectClass(type) || metadataUtil.isCompanionObjectClass(type); + } + + private static boolean hasOnlyStaticProvides(TypeElement module) { + // TODO(erichang): Check for @Produces too when we have a producers story + return ElementFilter.methodsIn(module.getEnclosedElements()).stream() + .filter(method -> Processors.hasAnnotation(method, ClassNames.PROVIDES)) + .allMatch(method -> method.getModifiers().contains(STATIC)); + } + private static boolean hasVisibleEmptyConstructor(TypeElement type) { List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements()); return constructors.isEmpty() diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD index 5da2241f5..ebbc941bc 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD +++ b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD @@ -39,13 +39,14 @@ java_library( "AggregatedDepsGenerator.java", "AggregatedDepsProcessor.java", "PkgPrivateEntryPointGenerator.java", + "PkgPrivateMetadata.java", "PkgPrivateModuleGenerator.java", ], deps = [ - ":pkg_private_metadata", + "//:dagger_with_compiler", + "//java/dagger/hilt/android/processor/internal/androidentrypoint:compiler_options", "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", - "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:components", "//java/dagger/hilt/processor/internal:kotlin", "//java/dagger/hilt/processor/internal:processor_errors", @@ -54,21 +55,10 @@ java_library( "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/guava:collect", "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/incap", - "@google_bazel_common//third_party/java/javapoet", - "@maven//:com_google_auto_auto_common", - ], -) - -java_library( - name = "pkg_private_metadata", - srcs = ["PkgPrivateMetadata.java"], - deps = [ - "//java/dagger/hilt/processor/internal:classnames", - "//java/dagger/hilt/processor/internal:kotlin", - "//java/dagger/hilt/processor/internal:processors", "@google_bazel_common//third_party/java/auto:value", + "@google_bazel_common//third_party/java/incap", "@google_bazel_common//third_party/java/javapoet", + "@google_bazel_common//third_party/java/jsr250_annotations", "@maven//:com_google_auto_auto_common", ], ) @@ -76,17 +66,14 @@ java_library( java_library( name = "component_dependencies", srcs = [ - "AggregatedDepsMetadata.java", "ComponentDependencies.java", ], deps = [ - ":pkg_private_metadata", - "//java/dagger/hilt/processor/internal:aggregated_elements", + ":processor_lib", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:component_descriptor", + "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata", - "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/guava:base", "//java/dagger/internal/guava:collect", diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java index 897ffd144..23ed148c0 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java @@ -16,17 +16,36 @@ package dagger.hilt.processor.internal.aggregateddeps; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsGenerator.AGGREGATING_PACKAGE; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.SetMultimap; import com.squareup.javapoet.ClassName; +import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.BadInputException; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ComponentDescriptor; -import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata; -import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata; +import dagger.hilt.processor.internal.ProcessorErrors; +import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies.AggregatedDepMetadata.DependencyType; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; @@ -46,23 +65,6 @@ public abstract class ComponentDependencies { /** Returns the component entry point associated with the given a component. */ public abstract Dependencies componentEntryPoints(); - /** Returns the set of early entry points */ - public abstract ImmutableSet<ClassName> earlyEntryPoints(); - - /** Returns {@code true} if any entry points are annotated with {@code EarlyEntryPoints}. */ - public boolean hasEarlyEntryPoints() { - return !earlyEntryPoints().isEmpty(); - } - - /** - * Returns {@code true} if the test binds or uninstalls test-specific bindings that would prevent - * it from sharing components with other test roots. - */ - public final boolean includesTestDeps(ClassName root) { - return modules().testDeps().keySet().stream().anyMatch((key) -> key.test().equals(root)) - || modules().uninstalledTestDeps().containsKey(root); - } - @AutoValue.Builder abstract static class Builder { abstract Dependencies.Builder modulesBuilder(); @@ -71,9 +73,12 @@ public abstract class ComponentDependencies { abstract Dependencies.Builder componentEntryPointsBuilder(); - abstract ImmutableSet.Builder<ClassName> earlyEntryPointsBuilder(); + abstract ComponentDependencies autoBuild(); - abstract ComponentDependencies build(); + ComponentDependencies build(Elements elements) { + validateModules(modulesBuilder().build(), elements); + return autoBuild(); + } } /** A key used for grouping a test dependency by both its component and test name. */ @@ -118,17 +123,6 @@ public abstract class ComponentDependencies { /** Returns the global uninstalled test deps. */ abstract ImmutableSet<TypeElement> globalUninstalledTestDeps(); - /** Returns the dependencies to be installed in the global singleton component. */ - ImmutableSet<TypeElement> getGlobalSingletonDeps() { - return ImmutableSet.<TypeElement>builder() - .addAll( - globalDeps().get(ClassNames.SINGLETON_COMPONENT).stream() - .filter(dep -> !globalUninstalledTestDeps().contains(dep)) - .collect(toImmutableSet())) - .addAll(globalTestDeps().get(ClassNames.SINGLETON_COMPONENT)) - .build(); - } - /** Returns the dependencies to be installed in the given component for the given root. */ public ImmutableSet<TypeElement> get(ClassName component, ClassName root, boolean isTestRoot) { if (!isTestRoot) { @@ -181,10 +175,14 @@ public abstract class ComponentDependencies { */ public static ComponentDependencies from( ImmutableSet<ComponentDescriptor> descriptors, Elements elements) { - ImmutableSet<ClassName> componentNames = - descriptors.stream().map(ComponentDescriptor::component).collect(toImmutableSet()); + Map<String, ComponentDescriptor> descriptorLookup = descriptorLookupMap(descriptors); + ImmutableList<AggregatedDepMetadata> metadatas = + getAggregatedDeps(elements).stream() + .map(deps -> AggregatedDepMetadata.create(deps, descriptorLookup, elements)) + .collect(toImmutableList()); + ComponentDependencies.Builder componentDependencies = ComponentDependencies.builder(); - for (AggregatedDepsMetadata metadata : AggregatedDepsMetadata.from(elements)) { + for (AggregatedDepMetadata metadata : metadatas) { Dependencies.Builder builder = null; switch (metadata.dependencyType()) { case MODULE: @@ -197,46 +195,265 @@ public abstract class ComponentDependencies { builder = componentDependencies.componentEntryPointsBuilder(); break; } - for (TypeElement componentElement : metadata.componentElements()) { - ClassName componentName = ClassName.get(componentElement); - checkState( - componentNames.contains(componentName), "%s is not a valid Component.", componentName); + + for (ComponentDescriptor componentDescriptor : metadata.componentDescriptors()) { + ClassName component = componentDescriptor.component(); if (metadata.testElement().isPresent()) { // In this case the @InstallIn or @TestInstallIn applies to only the given test root. ClassName test = ClassName.get(metadata.testElement().get()); - builder.testDepsBuilder().put(TestDepKey.of(componentName, test), metadata.dependency()); + builder.testDepsBuilder().put(TestDepKey.of(component, test), metadata.dependency()); builder.uninstalledTestDepsBuilder().putAll(test, metadata.replacedDependencies()); } else { // In this case the @InstallIn or @TestInstallIn applies to all roots if (!metadata.replacedDependencies().isEmpty()) { // If there are replacedDependencies() it means this is a @TestInstallIn - builder.globalTestDepsBuilder().put(componentName, metadata.dependency()); + builder.globalTestDepsBuilder().put(component, metadata.dependency()); builder.globalUninstalledTestDepsBuilder().addAll(metadata.replacedDependencies()); } else { - builder.globalDepsBuilder().put(componentName, metadata.dependency()); + builder.globalDepsBuilder().put(component, metadata.dependency()); } } } } - AggregatedUninstallModulesMetadata.from(elements) + // Collect all @UninstallModules. + // TODO(b/176438516): Filter @UninstallModules at the root. + metadatas.stream() + .filter(metadata -> metadata.testElement().isPresent()) + .map(metadata -> metadata.testElement().get()) + .distinct() + .filter(testElement -> Processors.hasAnnotation(testElement, ClassNames.IGNORE_MODULES)) .forEach( - metadata -> + testElement -> componentDependencies .modulesBuilder() .uninstalledTestDepsBuilder() .putAll( - ClassName.get(metadata.testElement()), - metadata.uninstallModuleElements().stream() - .map(module -> PkgPrivateMetadata.publicModule(module, elements)) - .collect(toImmutableSet()))); - - AggregatedEarlyEntryPointMetadata.from(elements).stream() - .map(AggregatedEarlyEntryPointMetadata::earlyEntryPoint) - .map(entryPoint -> PkgPrivateMetadata.publicEarlyEntryPoint(entryPoint, elements)) - .map(ClassName::get) - .forEach(componentDependencies.earlyEntryPointsBuilder()::add); - - return componentDependencies.build(); + ClassName.get(testElement), getUninstalledModules(testElement, elements))); + + return componentDependencies.build(elements); + } + + private static ImmutableMap<String, ComponentDescriptor> descriptorLookupMap( + ImmutableSet<ComponentDescriptor> descriptors) { + ImmutableMap.Builder<String, ComponentDescriptor> builder = ImmutableMap.builder(); + for (ComponentDescriptor descriptor : descriptors) { + // This is a temporary hack to map the old ApplicationComponent to the new SingletonComponent. + // Technically, this is only needed for backwards compatibility with libraries using the old + // processor since new processors should convert to the new SingletonComponent when generating + // the metadata class. + if (descriptor.component().equals(ClassNames.SINGLETON_COMPONENT)) { + builder.put("dagger.hilt.android.components.ApplicationComponent", descriptor); + } + builder.put(descriptor.component().toString(), descriptor); + } + return builder.build(); + } + + // Validate that the @UninstallModules doesn't contain any test modules. + private static Dependencies validateModules(Dependencies moduleDeps, Elements elements) { + SetMultimap<ClassName, TypeElement> invalidTestModules = HashMultimap.create(); + moduleDeps.testDeps().entries().stream() + .filter( + e -> moduleDeps.uninstalledTestDeps().containsEntry(e.getKey().test(), e.getValue())) + .forEach(e -> invalidTestModules.put(e.getKey().test(), e.getValue())); + + // Currently we don't have a good way to throw an error for all tests, so we sort (to keep the + // error reporting order stable) and then choose the first test. + // TODO(bcorso): Consider using ProcessorErrorHandler directly to report all errors at once? + Optional<ClassName> invalidTest = + invalidTestModules.keySet().stream() + .min((test1, test2) -> test1.toString().compareTo(test2.toString())); + if (invalidTest.isPresent()) { + throw new BadInputException( + String.format( + "@UninstallModules on test, %s, should not containing test modules, " + + "but found: %s", + invalidTest.get(), + invalidTestModules.get(invalidTest.get()).stream() + // Sort modules to keep stable error messages. + .sorted((test1, test2) -> test1.toString().compareTo(test2.toString())) + .collect(toImmutableList())), + elements.getTypeElement(invalidTest.get().toString())); + } + return moduleDeps; + } + + private static ImmutableSet<TypeElement> getUninstalledModules( + TypeElement testElement, Elements elements) { + ImmutableList<TypeElement> userUninstallModules = + Processors.getAnnotationClassValues( + elements, + Processors.getAnnotationMirror(testElement, ClassNames.IGNORE_MODULES), + "value"); + + // For pkg-private modules, find the generated wrapper class and uninstall that instead. + return userUninstallModules.stream() + .map(uninstallModule -> getPublicDependency(uninstallModule, elements)) + .collect(toImmutableSet()); + } + + /** Returns the public Hilt wrapper module, or the module itself if its already public. */ + private static TypeElement getPublicDependency(TypeElement dependency, Elements elements) { + return PkgPrivateMetadata.of(elements, dependency, ClassNames.MODULE) + .map(metadata -> elements.getTypeElement(metadata.generatedClassName().toString())) + .orElse(dependency); + } + + /** Returns the top-level elements of the aggregated deps package. */ + private static ImmutableList<AnnotationMirror> getAggregatedDeps(Elements elements) { + PackageElement packageElement = elements.getPackageElement(AGGREGATING_PACKAGE); + checkState( + packageElement != null, + "Couldn't find package %s. Did you mark your @Module classes with @InstallIn annotations?", + AGGREGATING_PACKAGE); + + List<? extends Element> aggregatedDepsElements = packageElement.getEnclosedElements(); + checkState( + !aggregatedDepsElements.isEmpty(), + "No dependencies found. Did you mark your @Module classes with @InstallIn annotations?"); + + ImmutableList.Builder<AnnotationMirror> builder = ImmutableList.builder(); + for (Element element : aggregatedDepsElements) { + ProcessorErrors.checkState( + element.getKind() == ElementKind.CLASS, + element, + "Only classes may be in package %s. Did you add custom code in the package?", + AGGREGATING_PACKAGE); + + AnnotationMirror aggregatedDeps = + Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_DEPS); + ProcessorErrors.checkState( + aggregatedDeps != null, + element, + "Classes in package %s must be annotated with @AggregatedDeps: %s. Found: %s.", + AGGREGATING_PACKAGE, + element.getSimpleName(), + element.getAnnotationMirrors()); + + builder.add(aggregatedDeps); + } + return builder.build(); + } + + @AutoValue + abstract static class AggregatedDepMetadata { + static AggregatedDepMetadata create( + AnnotationMirror aggregatedDeps, + Map<String, ComponentDescriptor> descriptorLookup, + Elements elements) { + ImmutableMap<String, AnnotationValue> aggregatedDepsValues = + Processors.getAnnotationValues(elements, aggregatedDeps); + + return new AutoValue_ComponentDependencies_AggregatedDepMetadata( + getTestElement(aggregatedDepsValues.get("test"), elements), + getComponents(aggregatedDepsValues.get("components"), descriptorLookup), + getDependencyType( + aggregatedDepsValues.get("modules"), + aggregatedDepsValues.get("entryPoints"), + aggregatedDepsValues.get("componentEntryPoints")), + getDependency( + aggregatedDepsValues.get("modules"), + aggregatedDepsValues.get("entryPoints"), + aggregatedDepsValues.get("componentEntryPoints"), + elements), + getReplacedDependencies(aggregatedDepsValues.get("replaces"), elements)); + } + + enum DependencyType { + MODULE, + ENTRY_POINT, + COMPONENT_ENTRY_POINT + } + + abstract Optional<TypeElement> testElement(); + + abstract ImmutableList<ComponentDescriptor> componentDescriptors(); + + abstract DependencyType dependencyType(); + + abstract TypeElement dependency(); + + abstract ImmutableSet<TypeElement> replacedDependencies(); + + private static Optional<TypeElement> getTestElement( + AnnotationValue testValue, Elements elements) { + checkNotNull(testValue); + String test = AnnotationValues.getString(testValue); + return test.isEmpty() ? Optional.empty() : Optional.of(elements.getTypeElement(test)); + } + + private static ImmutableList<ComponentDescriptor> getComponents( + AnnotationValue componentsValue, Map<String, ComponentDescriptor> descriptorLookup) { + checkNotNull(componentsValue); + ImmutableList<String> componentNames = + AnnotationValues.getAnnotationValues(componentsValue).stream() + .map(AnnotationValues::getString) + .collect(toImmutableList()); + + checkState(!componentNames.isEmpty()); + ImmutableList.Builder<ComponentDescriptor> components = ImmutableList.builder(); + for (String componentName : componentNames) { + checkState( + descriptorLookup.containsKey(componentName), + "%s is not a valid Component. Did you add or remove code in package %s?", + componentName, + AGGREGATING_PACKAGE); + components.add(descriptorLookup.get(componentName)); + } + return components.build(); + } + + private static DependencyType getDependencyType( + AnnotationValue modulesValue, + AnnotationValue entryPointsValue, + AnnotationValue componentEntryPointsValue) { + checkNotNull(modulesValue); + checkNotNull(entryPointsValue); + checkNotNull(componentEntryPointsValue); + + ImmutableSet.Builder<DependencyType> dependencyTypes = ImmutableSet.builder(); + if (!AnnotationValues.getAnnotationValues(modulesValue).isEmpty()) { + dependencyTypes.add(DependencyType.MODULE); + } + if (!AnnotationValues.getAnnotationValues(entryPointsValue).isEmpty()) { + dependencyTypes.add(DependencyType.ENTRY_POINT); + } + if (!AnnotationValues.getAnnotationValues(componentEntryPointsValue).isEmpty()) { + dependencyTypes.add(DependencyType.COMPONENT_ENTRY_POINT); + } + return getOnlyElement(dependencyTypes.build()); + } + + private static TypeElement getDependency( + AnnotationValue modulesValue, + AnnotationValue entryPointsValue, + AnnotationValue componentEntryPointsValue, + Elements elements) { + checkNotNull(modulesValue); + checkNotNull(entryPointsValue); + checkNotNull(componentEntryPointsValue); + + return elements.getTypeElement( + AnnotationValues.getString( + getOnlyElement( + ImmutableList.<AnnotationValue>builder() + .addAll(AnnotationValues.getAnnotationValues(modulesValue)) + .addAll(AnnotationValues.getAnnotationValues(entryPointsValue)) + .addAll(AnnotationValues.getAnnotationValues(componentEntryPointsValue)) + .build()))); + } + + private static ImmutableSet<TypeElement> getReplacedDependencies( + AnnotationValue replacedDependenciesValue, Elements elements) { + // Allow null values to support libraries using a Hilt version before @TestInstallIn was added + return replacedDependenciesValue == null + ? ImmutableSet.of() + : AnnotationValues.getAnnotationValues(replacedDependenciesValue).stream() + .map(AnnotationValues::getString) + .map(elements::getTypeElement) + .map(replacedDep -> getPublicDependency(replacedDep, elements)) + .collect(toImmutableSet()); + } } } diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java index ff344a651..66f175151 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java @@ -24,35 +24,16 @@ import com.google.auto.value.AutoValue; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; import dagger.hilt.processor.internal.ClassNames; -import dagger.hilt.processor.internal.KotlinMetadataUtils; import dagger.hilt.processor.internal.Processors; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; /** PkgPrivateModuleMetadata contains a set of utilities for processing package private modules. */ @AutoValue -public abstract class PkgPrivateMetadata { - /** Returns the public Hilt wrapped type or the type itself if it is already public. */ - public static TypeElement publicModule(TypeElement element, Elements elements) { - return publicDep(element, elements, ClassNames.MODULE); - } - - /** Returns the public Hilt wrapped type or the type itself if it is already public. */ - public static TypeElement publicEarlyEntryPoint(TypeElement element, Elements elements) { - return publicDep(element, elements, ClassNames.EARLY_ENTRY_POINT); - } - - private static TypeElement publicDep( - TypeElement element, Elements elements, ClassName annotation) { - return of(elements, element, annotation) - .map(PkgPrivateMetadata::generatedClassName) - .map(ClassName::canonicalName) - .map(elements::getTypeElement) - .orElse(element); - } - +abstract class PkgPrivateMetadata { private static final String PREFIX = "HiltWrapper_"; /** Returns the base class name of the elemenet. */ @@ -82,11 +63,9 @@ public abstract class PkgPrivateMetadata { * Returns an Optional PkgPrivateMetadata requiring Hilt processing, otherwise returns an empty * Optional. */ - static Optional<PkgPrivateMetadata> of( - Elements elements, TypeElement element, ClassName annotation) { + static Optional<PkgPrivateMetadata> of(Elements elements, Element element, ClassName annotation) { // If this is a public element no wrapping is needed - if (effectiveVisibilityOfElement(element) == Visibility.PUBLIC - && !KotlinMetadataUtils.getMetadataUtil().isVisibilityInternal(element)) { + if (effectiveVisibilityOfElement(element) == Visibility.PUBLIC) { return Optional.empty(); } |