From 27a28276b62b24d306b6f18248699e2173bd6600 Mon Sep 17 00:00:00 2001 From: Brad Corso Date: Tue, 13 Apr 2021 16:51:49 -0700 Subject: Add AggregatedEarlyEntryPointMetadata to aggregate information about @EarlyEntryPoints. Note: this is a breaking change in the rare case that an app that upgrades Hilt is relying on an @EarlyEntryPoint usage in a library that is not also updated. To fix, all usages of @EarlyEntryPoint must be using the upgraded version of Hilt or later. Otherwise, they may miss the @EarlyEntryPoint usage and fail at runtime during a test. RELNOTES=This is a breaking change in the rare case that an app that upgrades Hilt is relying on an @EarlyEntryPoint usage in a library that is not also updated. To fix, all usages of @EarlyEntryPoint must be using the upgraded version of Hilt or later. Otherwise, they may miss the @EarlyEntryPoint usage and fail at runtime during a test. PiperOrigin-RevId: 368322897 --- .../dagger/hilt/processor/internal/ClassNames.java | 8 +- .../hilt/processor/internal/aggregateddeps/BUILD | 1 + .../aggregateddeps/ComponentDependencies.java | 31 +++---- .../aggregateddeps/PkgPrivateMetadata.java | 14 +++- .../AggregatedEarlyEntryPointGenerator.java | 61 ++++++++++++++ .../AggregatedEarlyEntryPointMetadata.java | 98 ++++++++++++++++++++++ .../hilt/processor/internal/earlyentrypoint/BUILD | 63 ++++++++++++++ .../earlyentrypoint/EarlyEntryPointProcessor.java | 45 ++++++++++ .../hilt/processor/internal/root/RootMetadata.java | 8 +- .../processor/internal/root/RootProcessor.java | 2 +- 10 files changed, 306 insertions(+), 25 deletions(-) create mode 100644 java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java create mode 100644 java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java create mode 100644 java/dagger/hilt/processor/internal/earlyentrypoint/BUILD create mode 100644 java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java (limited to 'java/dagger/hilt/processor/internal') diff --git a/java/dagger/hilt/processor/internal/ClassNames.java b/java/dagger/hilt/processor/internal/ClassNames.java index ac6b34623..bc42e6c16 100644 --- a/java/dagger/hilt/processor/internal/ClassNames.java +++ b/java/dagger/hilt/processor/internal/ClassNames.java @@ -22,6 +22,12 @@ import com.squareup.javapoet.ClassName; /** Holder for commonly used class names. */ public final class ClassNames { + public static final String AGGREGATED_EARLY_ENTRY_POINT_PACKAGE = + "dagger.hilt.android.internal.earlyentrypoint.codegen"; + public static final ClassName AGGREGATED_EARLY_ENTRY_POINT = + get("dagger.hilt.android.internal.earlyentrypoint", "AggregatedEarlyEntryPoint"); + public static final ClassName EARLY_ENTRY_POINT = get("dagger.hilt.android", "EarlyEntryPoint"); + public static final String AGGREGATED_UNINSTALL_MODULES_PACKAGE = "dagger.hilt.android.internal.uninstallmodules.codegen"; public static final ClassName AGGREGATED_UNINSTALL_MODULES = @@ -120,8 +126,6 @@ public final class ClassNames { public static final ClassName APPLICATION = get("android.app", "Application"); public static final ClassName MULTI_DEX_APPLICATION = get("androidx.multidex", "MultiDexApplication"); - public static final ClassName EARLY_ENTRY_POINT = - get("dagger.hilt.android", "EarlyEntryPoint"); public static final ClassName ANDROID_ENTRY_POINT = get("dagger.hilt.android", "AndroidEntryPoint"); public static final ClassName HILT_ANDROID_APP = diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD index ee0c10151..2797a838e 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD +++ b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD @@ -85,6 +85,7 @@ java_library( "//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", diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java index 9c4d0df74..897ffd144 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java @@ -17,18 +17,15 @@ package dagger.hilt.processor.internal.aggregateddeps; import static com.google.common.base.Preconditions.checkState; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; -import com.google.auto.value.extension.memoized.Memoized; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ComponentDescriptor; -import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata; import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; @@ -49,11 +46,12 @@ 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 earlyEntryPoints(); + /** Returns {@code true} if any entry points are annotated with {@code EarlyEntryPoints}. */ - @Memoized - public boolean hasEarlySingletonEntryPoints() { - return entryPoints().getGlobalSingletonDeps().stream() - .anyMatch(entryPoint -> Processors.hasAnnotation(entryPoint, ClassNames.EARLY_ENTRY_POINT)); + public boolean hasEarlyEntryPoints() { + return !earlyEntryPoints().isEmpty(); } /** @@ -73,6 +71,8 @@ public abstract class ComponentDependencies { abstract Dependencies.Builder componentEntryPointsBuilder(); + abstract ImmutableSet.Builder earlyEntryPointsBuilder(); + abstract ComponentDependencies build(); } @@ -181,9 +181,8 @@ public abstract class ComponentDependencies { */ public static ComponentDependencies from( ImmutableSet descriptors, Elements elements) { - ImmutableMap descriptorLookup = - descriptors.stream() - .collect(toImmutableMap(ComponentDescriptor::component, descriptor -> descriptor)); + ImmutableSet componentNames = + descriptors.stream().map(ComponentDescriptor::component).collect(toImmutableSet()); ComponentDependencies.Builder componentDependencies = ComponentDependencies.builder(); for (AggregatedDepsMetadata metadata : AggregatedDepsMetadata.from(elements)) { Dependencies.Builder builder = null; @@ -201,9 +200,7 @@ public abstract class ComponentDependencies { for (TypeElement componentElement : metadata.componentElements()) { ClassName componentName = ClassName.get(componentElement); checkState( - descriptorLookup.containsKey(componentName), - "%s is not a valid Component.", - componentName); + componentNames.contains(componentName), "%s is not a valid Component.", componentName); 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()); @@ -234,6 +231,12 @@ public abstract class ComponentDependencies { .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(); } } diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java index 9c990756c..ff344a651 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java @@ -34,9 +34,19 @@ 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 wrapper module, or the module itself if its already public. */ + /** Returns the public Hilt wrapped type or the type itself if it is already public. */ public static TypeElement publicModule(TypeElement element, Elements elements) { - return of(elements, element, ClassNames.MODULE) + 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) diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java new file mode 100644 index 000000000..84ed6b266 --- /dev/null +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java @@ -0,0 +1,61 @@ +/* + * 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.earlyentrypoint; + +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.ClassNames; +import dagger.hilt.processor.internal.Processors; +import java.io.IOException; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +/** + * Generates an {@link dagger.hilt.android.internal.earlyentrypoint.AggregatedEarlyEntryPoint} + * annotation. + */ +final class AggregatedEarlyEntryPointGenerator { + + private final ProcessingEnvironment env; + private final TypeElement earlyEntryPoint; + + AggregatedEarlyEntryPointGenerator(TypeElement earlyEntryPoint, ProcessingEnvironment env) { + this.earlyEntryPoint = earlyEntryPoint; + this.env = env; + } + + void generate() throws IOException { + ClassName name = + ClassName.get( + ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE, + Processors.getFullEnclosedName(earlyEntryPoint) + "_AggregatedEarlyEntryPoint"); + + TypeSpec.Builder builder = + TypeSpec.classBuilder(name) + .addOriginatingElement(earlyEntryPoint) + .addAnnotation( + AnnotationSpec.builder(ClassNames.AGGREGATED_EARLY_ENTRY_POINT) + .addMember("earlyEntryPoint", "$S", earlyEntryPoint.getQualifiedName()) + .build()); + + Processors.addGeneratedAnnotation(builder, env, getClass()); + + JavaFile.builder(name.packageName(), builder.build()).build().writeTo(env.getFiler()); + } +} diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java new file mode 100644 index 000000000..fb0460a97 --- /dev/null +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java @@ -0,0 +1,98 @@ +/* + * 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.earlyentrypoint; + +import com.google.auto.common.MoreElements; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.ProcessorErrors; +import dagger.hilt.processor.internal.Processors; +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; + +/** + * A class that represents the values stored in an {@link + * dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules} annotation. + */ +@AutoValue +public abstract class AggregatedEarlyEntryPointMetadata { + + /** Returns the element annotated with {@link dagger.hilt.android.EarlyEntryPoint}. */ + public abstract TypeElement earlyEntryPoint(); + + /** Returns all aggregated deps in the aggregating package. */ + public static ImmutableSet from(Elements elements) { + PackageElement packageElement = + elements.getPackageElement(ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE); + + if (packageElement == null) { + return ImmutableSet.of(); + } + + ImmutableSet aggregatedElements = + ImmutableSet.copyOf(packageElement.getEnclosedElements()); + + ProcessorErrors.checkState( + !aggregatedElements.isEmpty(), + packageElement, + "No dependencies found. Did you remove code in package %s?", + ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE); + + ImmutableSet.Builder builder = ImmutableSet.builder(); + for (Element element : aggregatedElements) { + ProcessorErrors.checkState( + element.getKind() == ElementKind.CLASS, + element, + "Only classes may be in package %s. Did you add custom code in the package?", + ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE); + + builder.add(create(MoreElements.asType(element), elements)); + } + + return builder.build(); + } + + private static AggregatedEarlyEntryPointMetadata create(TypeElement element, Elements elements) { + AnnotationMirror annotationMirror = + Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_EARLY_ENTRY_POINT); + + ProcessorErrors.checkState( + annotationMirror != null, + element, + "Classes in package %s must be annotated with @%s: %s. Found: %s.", + ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE, + ClassNames.AGGREGATED_EARLY_ENTRY_POINT, + element.getSimpleName(), + element.getAnnotationMirrors()); + + ImmutableMap values = + Processors.getAnnotationValues(elements, annotationMirror); + + TypeElement earlyEntryPoint = + elements.getTypeElement(AnnotationValues.getString(values.get("earlyEntryPoint"))); + + return new AutoValue_AggregatedEarlyEntryPointMetadata(earlyEntryPoint); + } +} diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD new file mode 100644 index 000000000..241121849 --- /dev/null +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD @@ -0,0 +1,63 @@ +# Copyright (C) 2020 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. + +# Description: +# A processor for @dagger.hilt.android.EarlyEntryPoint. + +package(default_visibility = ["//:src"]) + +java_plugin( + name = "processor", + generates_api = 1, + processor_class = "dagger.hilt.processor.internal.earlyentrypoint.EarlyEntryPointProcessor", + deps = [":processor_lib"], +) + +java_library( + name = "processor_lib", + srcs = [ + "AggregatedEarlyEntryPointGenerator.java", + "EarlyEntryPointProcessor.java", + ], + deps = [ + "//java/dagger/hilt/processor/internal:base_processor", + "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:processors", + "//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 = "aggregated_early_entry_point_metadata", + srcs = [ + "AggregatedEarlyEntryPointMetadata.java", + ], + deps = [ + "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:processor_errors", + "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/internal/guava:collect", + "@google_bazel_common//third_party/java/auto:value", + "@maven//:com_google_auto_auto_common", + ], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java new file mode 100644 index 000000000..848fa319d --- /dev/null +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java @@ -0,0 +1,45 @@ +/* + * 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.earlyentrypoint; + +import static com.google.auto.common.MoreElements.asType; +import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.BaseProcessor; +import dagger.hilt.processor.internal.ClassNames; +import javax.annotation.processing.Processor; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; + +/** Validates {@link dagger.hilt.android.EarlyEntryPoint} usages. */ +@IncrementalAnnotationProcessor(ISOLATING) +@AutoService(Processor.class) +public final class EarlyEntryPointProcessor extends BaseProcessor { + + @Override + public ImmutableSet getSupportedAnnotationTypes() { + return ImmutableSet.of(ClassNames.EARLY_ENTRY_POINT.toString()); + } + + @Override + public void processEach(TypeElement annotation, Element element) throws Exception { + new AggregatedEarlyEntryPointGenerator(asType(element), getProcessingEnv()).generate(); + } +} diff --git a/java/dagger/hilt/processor/internal/root/RootMetadata.java b/java/dagger/hilt/processor/internal/root/RootMetadata.java index 76d1d1495..1a115034f 100644 --- a/java/dagger/hilt/processor/internal/root/RootMetadata.java +++ b/java/dagger/hilt/processor/internal/root/RootMetadata.java @@ -219,13 +219,9 @@ public final class RootMetadata { .flatMap(metadata -> metadata.entryPoints(componentName).stream()) .forEach(entryPointSet::add); } else if (root.isDefaultRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT)) { - // Filter to only use the entry points annotated with @EarlyEntryPoint. We only - // do this for SingletonComponent because EarlyEntryPoints can only be installed + // We only do this for SingletonComponent because EarlyEntryPoints can only be installed // in the SingletonComponent. - deps.entryPoints().get(componentName, root.classname(), root.isTestRoot()).stream() - .filter(ep -> Processors.hasAnnotation(ep, ClassNames.EARLY_ENTRY_POINT)) - .map(ClassName::get) - .forEach(entryPointSet::add); + deps.earlyEntryPoints().forEach(entryPointSet::add); } else { deps.entryPoints().get(componentName, root.classname(), root.isTestRoot()).stream() .map(ClassName::get) diff --git a/java/dagger/hilt/processor/internal/root/RootProcessor.java b/java/dagger/hilt/processor/internal/root/RootProcessor.java index de5bb04b2..5a7ea33fc 100644 --- a/java/dagger/hilt/processor/internal/root/RootProcessor.java +++ b/java/dagger/hilt/processor/internal/root/RootProcessor.java @@ -165,7 +165,7 @@ public final class RootProcessor extends BaseProcessor { .filter(RootMetadata::canShareTestComponents) .collect(toImmutableList()); generateTestComponentData(rootMetadatas, componentNames); - if (deps.hasEarlySingletonEntryPoints() || !rootsThatCanShareComponents.isEmpty()) { + if (deps.hasEarlyEntryPoints() || !rootsThatCanShareComponents.isEmpty()) { Root defaultRoot = Root.createDefaultRoot(getProcessingEnv()); generateComponents( RootMetadata.createForDefaultRoot( -- cgit v1.2.3