From fcab18faa38bc06a405acdd18f107d8266929b45 Mon Sep 17 00:00:00 2001 From: Kuan-Ying Chou Date: Fri, 9 Jun 2023 06:03:11 -0700 Subject: [PATCH] [WIP] Migrate the remaining Hilt processors to XProcessingStep RELNOTES=N/A PiperOrigin-RevId: 539059066 --- .../AndroidEntryPointProcessingStep.java | 111 +++++ .../AndroidEntryPointProcessor.java | 87 +--- .../internal/androidentrypoint/BUILD | 6 +- .../bindvalue/BindValueProcessingStep.java | 5 +- .../processor/internal/viewmodel/BUILD | 1 + .../viewmodel/ViewModelProcessingStep.kt | 52 +++ .../internal/viewmodel/ViewModelProcessor.kt | 35 +- .../internal/BaseProcessingStep.java | 35 +- .../JavacBaseProcessingStepProcessor.java | 11 + .../AggregatedDepsProcessingStep.java | 406 ++++++++++++++++++ .../AggregatedDepsProcessor.java | 385 +---------------- .../processor/internal/aggregateddeps/BUILD | 2 +- .../processor/internal/definecomponent/BUILD | 1 + .../DefineComponentProcessingStep.java | 88 ++++ .../DefineComponentProcessor.java | 61 +-- .../dagger/hilt/processor/internal/root/BUILD | 2 +- .../root/ComponentTreeDepsProcessingStep.java | 193 +++++++++ .../root/ComponentTreeDepsProcessor.java | 172 +------- .../DefineComponentProcessorTest.java | 4 +- 19 files changed, 920 insertions(+), 737 deletions(-) create mode 100644 java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessingStep.java create mode 100644 java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt create mode 100644 java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessingStep.java create mode 100644 java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessingStep.java create mode 100644 java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessingStep.java diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessingStep.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessingStep.java new file mode 100644 index 00000000000..b92b806eb3e --- /dev/null +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessingStep.java @@ -0,0 +1,111 @@ +/* + * 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. + */ + +package dagger.hilt.android.processor.internal.androidentrypoint; + +import static dagger.hilt.processor.internal.HiltCompilerOptions.getGradleProjectType; +import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XProcessingEnv; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import dagger.hilt.android.processor.internal.AndroidClassNames; +import dagger.hilt.processor.internal.BaseProcessingStep; +import dagger.hilt.processor.internal.ProcessorErrors; +import dagger.hilt.processor.internal.optionvalues.GradleProjectType; + +/** + * Processor that creates a module for classes marked with {@link + * dagger.hilt.android.AndroidEntryPoint}. + */ +public final class AndroidEntryPointProcessingStep extends BaseProcessingStep { + public AndroidEntryPointProcessingStep(XProcessingEnv env) { + super(env); + } + + @Override + protected ImmutableSet annotationClassNames() { + return ImmutableSet.of( + AndroidClassNames.ANDROID_ENTRY_POINT, AndroidClassNames.HILT_ANDROID_APP); + } + + @Override + public boolean delayErrors() { + return true; + } + + @Override + public void processEach(ClassName annotation, XElement element) throws Exception { + AndroidEntryPointMetadata metadata = AndroidEntryPointMetadata.of(element); + new InjectorEntryPointGenerator(processingEnv(), metadata).generate(); + switch (metadata.androidType()) { + case APPLICATION: + GradleProjectType projectType = getGradleProjectType(processingEnv()); + if (projectType != GradleProjectType.UNSET) { + ProcessorErrors.checkState( + projectType == GradleProjectType.APP, + element, + "Application class, %s, annotated with @HiltAndroidApp must be defined in a " + + "Gradle android application module (i.e. contains a build.gradle file with " + + "`plugins { id 'com.android.application' }`).", + metadata.element().getQualifiedName()); + } + + // The generated application references the generated component so they must be generated + // in the same build unit. Thus, we only generate the application here if we're using the + // aggregating root processor. If we're using the Hilt Gradle plugin's aggregating task, we + // need to generate the application within ComponentTreeDepsProcessor instead. + if (useAggregatingRootProcessor(processingEnv())) { + // While we could always generate the application in ComponentTreeDepsProcessor, even if + // we're using the aggregating root processor, it can lead to extraneous errors when + // things fail before ComponentTreeDepsProcessor runs so we generate it here to avoid that + new ApplicationGenerator(processingEnv(), metadata).generate(); + } else { + // If we're not using the aggregating root processor, then make sure the root application + // does not extend the generated application directly, and instead uses bytecode injection + ProcessorErrors.checkState( + metadata.requiresBytecodeInjection(), + metadata.element(), + "'enableAggregatingTask=true' cannot be used when the application directly " + + "references the generated Hilt class, %s. Either extend %s directly (relying " + + "on the Gradle plugin described in " + + "https://dagger.dev/hilt/gradle-setup#why-use-the-plugin or set " + + "'enableAggregatingTask=false'.", + metadata.generatedClassName(), + metadata.baseClassName()); + } + break; + case ACTIVITY: + new ActivityGenerator(processingEnv(), metadata).generate(); + break; + case BROADCAST_RECEIVER: + new BroadcastReceiverGenerator(processingEnv(), metadata).generate(); + break; + case FRAGMENT: + new FragmentGenerator(processingEnv(), metadata).generate(); + break; + case SERVICE: + new ServiceGenerator(processingEnv(), metadata).generate(); + break; + case VIEW: + new ViewGenerator(processingEnv(), metadata).generate(); + break; + default: + throw new IllegalStateException("Unknown Hilt type: " + metadata.androidType()); + } + } +} diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java index 64ff097362f..e7c38310623 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java @@ -16,19 +16,11 @@ package dagger.hilt.android.processor.internal.androidentrypoint; -import static dagger.hilt.processor.internal.HiltCompilerOptions.getGradleProjectType; -import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; -import androidx.room.compiler.processing.XElement; -import androidx.room.compiler.processing.XTypeElement; import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableSet; -import dagger.hilt.android.processor.internal.AndroidClassNames; -import dagger.hilt.processor.internal.BaseProcessor; -import dagger.hilt.processor.internal.ProcessorErrors; -import dagger.hilt.processor.internal.optionvalues.GradleProjectType; -import java.util.Set; +import dagger.hilt.processor.internal.BaseProcessingStep; +import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor; import javax.annotation.processing.Processor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; @@ -38,78 +30,9 @@ */ @IncrementalAnnotationProcessor(ISOLATING) @AutoService(Processor.class) -public final class AndroidEntryPointProcessor extends BaseProcessor { - - @Override - public Set getSupportedAnnotationTypes() { - return ImmutableSet.of( - AndroidClassNames.ANDROID_ENTRY_POINT.toString(), - AndroidClassNames.HILT_ANDROID_APP.toString()); - } - +public final class AndroidEntryPointProcessor extends JavacBaseProcessingStepProcessor { @Override - public boolean delayErrors() { - return true; - } - - @Override - public void processEach(XTypeElement annotation, XElement element) throws Exception { - AndroidEntryPointMetadata metadata = AndroidEntryPointMetadata.of(element); - new InjectorEntryPointGenerator(processingEnv(), metadata).generate(); - switch (metadata.androidType()) { - case APPLICATION: - GradleProjectType projectType = getGradleProjectType(processingEnv()); - if (projectType != GradleProjectType.UNSET) { - ProcessorErrors.checkState( - projectType == GradleProjectType.APP, - element, - "Application class, %s, annotated with @HiltAndroidApp must be defined in a " - + "Gradle android application module (i.e. contains a build.gradle file with " - + "`plugins { id 'com.android.application' }`).", - metadata.element().getQualifiedName()); - } - - // The generated application references the generated component so they must be generated - // in the same build unit. Thus, we only generate the application here if we're using the - // aggregating root processor. If we're using the Hilt Gradle plugin's aggregating task, we - // need to generate the application within ComponentTreeDepsProcessor instead. - if (useAggregatingRootProcessor(processingEnv())) { - // While we could always generate the application in ComponentTreeDepsProcessor, even if - // we're using the aggregating root processor, it can lead to extraneous errors when - // things fail before ComponentTreeDepsProcessor runs so we generate it here to avoid that - new ApplicationGenerator(processingEnv(), metadata).generate(); - } else { - // If we're not using the aggregating root processor, then make sure the root application - // does not extend the generated application directly, and instead uses bytecode injection - ProcessorErrors.checkState( - metadata.requiresBytecodeInjection(), - metadata.element(), - "'enableAggregatingTask=true' cannot be used when the application directly " - + "references the generated Hilt class, %s. Either extend %s directly (relying " - + "on the Gradle plugin described in " - + "https://dagger.dev/hilt/gradle-setup#why-use-the-plugin or set " - + "'enableAggregatingTask=false'.", - metadata.generatedClassName(), - metadata.baseClassName()); - } - break; - case ACTIVITY: - new ActivityGenerator(processingEnv(), metadata).generate(); - break; - case BROADCAST_RECEIVER: - new BroadcastReceiverGenerator(processingEnv(), metadata).generate(); - break; - case FRAGMENT: - new FragmentGenerator(processingEnv(), metadata).generate(); - break; - case SERVICE: - new ServiceGenerator(processingEnv(), metadata).generate(); - break; - case VIEW: - new ViewGenerator(processingEnv(), metadata).generate(); - break; - default: - throw new IllegalStateException("Unknown Hilt type: " + metadata.androidType()); - } + protected BaseProcessingStep processingStep() { + return new AndroidEntryPointProcessingStep(getXProcessingEnv()); } } diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD index fdf5115b9c2..9f354248779 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD @@ -32,7 +32,10 @@ java_plugin( java_library( name = "processor_lib", - srcs = ["AndroidEntryPointProcessor.java"], + srcs = [ + "AndroidEntryPointProcessingStep.java", + "AndroidEntryPointProcessor.java", + ], deps = [ ":android_generators", ":metadata", @@ -45,6 +48,7 @@ java_library( "//third_party/java/auto:service", "//third_party/java/guava/collect", "//third_party/java/incap", + "//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessingStep.java b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessingStep.java index cc569b563b7..a55521261c6 100644 --- a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessingStep.java +++ b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessingStep.java @@ -23,6 +23,7 @@ import androidx.room.compiler.processing.XAnnotation; import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XRoundEnv; import androidx.room.compiler.processing.XTypeElement; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; @@ -59,7 +60,7 @@ protected ImmutableSet annotationClassNames() { } @Override - protected void preProcess() { + protected void preProcess(XProcessingEnv env, XRoundEnv round) { testRootMap.clear(); } @@ -85,7 +86,7 @@ && asTypeElement(enclosingElement).isClass() } @Override - public void postProcess() throws Exception { + protected void postProcess(XProcessingEnv env, XRoundEnv round) throws Exception { // Generate a module for each testing class with a @BindValue field. for (Map.Entry> e : testRootMap.asMap().entrySet()) { BindValueMetadata metadata = BindValueMetadata.create(e.getKey(), e.getValue()); diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/BUILD b/java/dagger/hilt/android/processor/internal/viewmodel/BUILD index 3028c548197..90eae5aea37 100644 --- a/java/dagger/hilt/android/processor/internal/viewmodel/BUILD +++ b/java/dagger/hilt/android/processor/internal/viewmodel/BUILD @@ -30,6 +30,7 @@ kt_jvm_library( srcs = [ "ViewModelMetadata.kt", "ViewModelModuleGenerator.kt", + "ViewModelProcessingStep.kt", "ViewModelProcessor.kt", ], deps = [ diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt new file mode 100644 index 00000000000..5acc9022541 --- /dev/null +++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package dagger.hilt.android.processor.internal.viewmodel + +import androidx.room.compiler.processing.ExperimentalProcessingApi +import androidx.room.compiler.processing.XElement +import androidx.room.compiler.processing.XProcessingEnv +import androidx.room.compiler.processing.XRoundEnv +import androidx.room.compiler.processing.XTypeElement +import com.squareup.javapoet.ClassName +import dagger.hilt.android.processor.internal.AndroidClassNames +import dagger.hilt.processor.internal.BaseProcessingStep +import dagger.internal.codegen.xprocessing.XElements + +@OptIn(ExperimentalProcessingApi::class) +/** Annotation processor for @ViewModelInject. */ +class ViewModelProcessingStep(env: XProcessingEnv) : BaseProcessingStep(env) { + private val parsedElements = mutableSetOf() + + override fun annotationClassNames(): Set = setOf(AndroidClassNames.HILT_VIEW_MODEL) + + override fun processEach(annotation: ClassName, element: XElement) { + val typeElement = XElements.asTypeElement(element) + if (parsedElements.add(typeElement)) { + ViewModelMetadata.create( + processingEnv(), + typeElement, + ) + ?.let { viewModelMetadata -> + ViewModelModuleGenerator(processingEnv(), viewModelMetadata).generate() + } + } + } + + override fun postProcess(env: XProcessingEnv?, round: XRoundEnv?) { + parsedElements.clear() + } +} diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessor.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessor.kt index 2d55f7f8150..fd63e6ef802 100644 --- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessor.kt +++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessor.kt @@ -17,44 +17,17 @@ package dagger.hilt.android.processor.internal.viewmodel import androidx.room.compiler.processing.ExperimentalProcessingApi -import androidx.room.compiler.processing.XElement -import androidx.room.compiler.processing.XRoundEnv -import androidx.room.compiler.processing.XTypeElement import com.google.auto.service.AutoService -import dagger.hilt.android.processor.internal.AndroidClassNames -import dagger.hilt.processor.internal.BaseProcessor -import dagger.internal.codegen.xprocessing.XElements +import dagger.hilt.processor.internal.BaseProcessingStep +import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor import javax.annotation.processing.Processor -import javax.lang.model.SourceVersion import net.ltgt.gradle.incap.IncrementalAnnotationProcessor import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType /** Annotation processor for @ViewModelInject. */ @AutoService(Processor::class) @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) -class ViewModelProcessor : BaseProcessor() { - - private val parsedElements = mutableSetOf() - - override fun getSupportedAnnotationTypes() = setOf(AndroidClassNames.HILT_VIEW_MODEL.toString()) - - override fun getSupportedSourceVersion() = SourceVersion.latest() - +class ViewModelProcessor : JavacBaseProcessingStepProcessor() { @OptIn(ExperimentalProcessingApi::class) - override fun processEach(annotation: XTypeElement, element: XElement) { - val typeElement = XElements.asTypeElement(element) - if (parsedElements.add(typeElement)) { - ViewModelMetadata.create( - processingEnv(), - typeElement, - ) - ?.let { viewModelMetadata -> - ViewModelModuleGenerator(processingEnv(), viewModelMetadata).generate() - } - } - } - - override fun postRoundProcess(roundEnv: XRoundEnv) { - parsedElements.clear() - } + override fun processingStep(): BaseProcessingStep = ViewModelProcessingStep(xProcessingEnv) } diff --git a/java/dagger/hilt/processor/internal/BaseProcessingStep.java b/java/dagger/hilt/processor/internal/BaseProcessingStep.java index 56dd44953c9..e0041316f04 100644 --- a/java/dagger/hilt/processor/internal/BaseProcessingStep.java +++ b/java/dagger/hilt/processor/internal/BaseProcessingStep.java @@ -22,6 +22,7 @@ import androidx.room.compiler.processing.XElement; import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XProcessingStep; +import androidx.room.compiler.processing.XRoundEnv; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; @@ -69,18 +70,34 @@ public final ImmutableSet annotations() { protected abstract Set annotationClassNames(); - protected void preProcess() {} + protected abstract void processEach(ClassName annotation, XElement element) throws Exception; - public abstract void processEach(ClassName annotation, XElement element) throws Exception; + protected void preProcess(XProcessingEnv env, XRoundEnv round) {} - protected void postProcess() throws Exception {} + protected void postProcess(XProcessingEnv env, XRoundEnv round) throws Exception {} + + public final void preRoundProcess(XProcessingEnv env, XRoundEnv round) { + preProcess(env, round); + } + + public final void postRoundProcess(XProcessingEnv env, XRoundEnv round) { + if (errorHandler.isEmpty()) { + try { + postProcess(env, round); + } catch (Exception e) { + errorHandler.recordError(e); + } + } + if (!delayErrors() || round.isProcessingOver()) { + errorHandler.checkErrors(); + } + } @Override public final ImmutableSet process( XProcessingEnv env, Map> elementsByAnnotation, boolean isLastRound) { - preProcess(); ImmutableMap annotationClassNamesByName = annotationClassNames().stream() .collect(toImmutableMap(ClassName::canonicalName, Function.identity())); @@ -102,16 +119,6 @@ public final ImmutableSet process( } } } - if (errorHandler.isEmpty()) { - try { - postProcess(); - } catch (Exception e) { - errorHandler.recordError(e); - } - } - if (!delayErrors() || isLastRound) { - errorHandler.checkErrors(); - } return elementsToReprocessBuilder.build(); } diff --git a/java/dagger/hilt/processor/internal/JavacBaseProcessingStepProcessor.java b/java/dagger/hilt/processor/internal/JavacBaseProcessingStepProcessor.java index 898768f4027..47487856d62 100644 --- a/java/dagger/hilt/processor/internal/JavacBaseProcessingStepProcessor.java +++ b/java/dagger/hilt/processor/internal/JavacBaseProcessingStepProcessor.java @@ -19,6 +19,7 @@ import androidx.room.compiler.processing.XProcessingEnv; import androidx.room.compiler.processing.XProcessingEnvConfig; import androidx.room.compiler.processing.XProcessingStep; +import androidx.room.compiler.processing.XRoundEnv; import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -64,8 +65,18 @@ public final ImmutableList processingSteps() { return ImmutableList.of(processingStep); } + @Override + public void preRound(XProcessingEnv env, XRoundEnv round) { + processingStep.preRoundProcess(env, round); + } + protected abstract BaseProcessingStep processingStep(); + @Override + public void postRound(XProcessingEnv env, XRoundEnv round) { + processingStep.postRoundProcess(env, round); + } + /** Returns additional processing options that should only be applied for a single processor. */ protected Set additionalProcessingOptions() { return ImmutableSet.of(); diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessingStep.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessingStep.java new file mode 100644 index 00000000000..f19ec743a11 --- /dev/null +++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessingStep.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2019 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.collect.Iterables.getOnlyElement; +import static dagger.hilt.processor.internal.HiltCompilerOptions.isModuleInstallInCheckDisabled; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XElementKt; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import dagger.hilt.processor.internal.BaseProcessingStep; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Components; +import dagger.hilt.processor.internal.ProcessorErrors; +import dagger.hilt.processor.internal.Processors; +import dagger.internal.codegen.extension.DaggerStreams; +import dagger.internal.codegen.xprocessing.XElements; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +/** Processor that outputs dummy files to propagate information through multiple javac runs. */ +public final class AggregatedDepsProcessingStep extends BaseProcessingStep { + + private static final ImmutableSet ENTRY_POINT_ANNOTATIONS = + ImmutableSet.of( + ClassNames.ENTRY_POINT, + ClassNames.EARLY_ENTRY_POINT, + ClassNames.GENERATED_ENTRY_POINT, + ClassNames.COMPONENT_ENTRY_POINT); + + private static final ImmutableSet MODULE_ANNOTATIONS = + ImmutableSet.of( + ClassNames.MODULE); + + private static final ImmutableSet INSTALL_IN_ANNOTATIONS = + ImmutableSet.of(ClassNames.INSTALL_IN, ClassNames.TEST_INSTALL_IN); + + private final Set seen = new HashSet<>(); + + public AggregatedDepsProcessingStep(XProcessingEnv env) { + super(env); + } + + @Override + protected ImmutableSet annotationClassNames() { + return ImmutableSet.builder() + .addAll(INSTALL_IN_ANNOTATIONS) + .addAll(MODULE_ANNOTATIONS) + .addAll(ENTRY_POINT_ANNOTATIONS) + .build(); + } + + @Override + public void processEach(ClassName annotation, XElement element) throws Exception { + if (!seen.add(element)) { + return; + } + + Optional installInAnnotation = getAnnotation(element, INSTALL_IN_ANNOTATIONS); + Optional entryPointAnnotation = getAnnotation(element, ENTRY_POINT_ANNOTATIONS); + Optional moduleAnnotation = getAnnotation(element, MODULE_ANNOTATIONS); + + boolean hasInstallIn = installInAnnotation.isPresent(); + boolean isEntryPoint = entryPointAnnotation.isPresent(); + boolean isModule = moduleAnnotation.isPresent(); + + ProcessorErrors.checkState( + !hasInstallIn || isEntryPoint || isModule, + element, + "@%s-annotated classes must also be annotated with @Module or @EntryPoint: %s", + installInAnnotation.map(ClassName::simpleName).orElse("@InstallIn"), + XElements.toStableString(element)); + + ProcessorErrors.checkState( + !(isEntryPoint && isModule), + element, + "@%s and @%s cannot be used on the same interface: %s", + moduleAnnotation.map(ClassName::simpleName).orElse("@Module"), + entryPointAnnotation.map(ClassName::simpleName).orElse("@EntryPoint"), + XElements.toStableString(element)); + + if (isModule) { + processModule(element, installInAnnotation, moduleAnnotation.get()); + } else if (isEntryPoint) { + processEntryPoint(element, installInAnnotation, entryPointAnnotation.get()); + } else { + throw new AssertionError(); + } + } + + private void processModule( + XElement element, Optional installInAnnotation, ClassName moduleAnnotation) + throws Exception { + ProcessorErrors.checkState( + installInAnnotation.isPresent() + || isDaggerGeneratedModule(element) + || installInCheckDisabled(element), + element, + "%s is missing an @InstallIn annotation. If this was intentional, see" + + " https://dagger.dev/hilt/flags#disable-install-in-check for how to disable this" + + " check.", + XElements.toStableString(element)); + + if (!installInAnnotation.isPresent()) { + // Modules without @InstallIn or @TestInstallIn annotations don't need to be processed further + return; + } + + ProcessorErrors.checkState( + XElementKt.isTypeElement(element), + element, + "Only classes and interfaces can be annotated with @Module: %s", + XElements.toStableString(element)); + + XTypeElement module = XElements.asTypeElement(element); + + ProcessorErrors.checkState( + module.isClass() || module.isInterface() || module.isKotlinObject(), + module, + "Only classes and interfaces can be annotated with @Module: %s", + XElements.toStableString(module)); + + ProcessorErrors.checkState( + Processors.isTopLevel(module) + || module.isStatic() + || module.isAbstract() + || module.getEnclosingElement().hasAnnotation(ClassNames.HILT_ANDROID_TEST), + module, + "Nested @%s modules must be static unless they are directly nested within a test. " + + "Found: %s", + installInAnnotation.get().simpleName(), + XElements.toStableString(module)); + + // 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(module.getClassName()) + || !Processors.requiresModuleInstance(module) + || Processors.hasVisibleEmptyConstructor(module), + module, + "Modules that need to be instantiated by Hilt must have a visible, empty constructor."); + + // TODO(b/28989613): This should really be fixed in Dagger. Remove once Dagger bug is fixed. + ImmutableList abstractMethodsWithMissingBinds = + module.getDeclaredMethods().stream() + .filter(XMethodElement::isAbstract) + .filter(method -> !Processors.hasDaggerAbstractMethodAnnotation(method)) + .collect(toImmutableList()); + ProcessorErrors.checkState( + abstractMethodsWithMissingBinds.isEmpty(), + module, + "Found unimplemented abstract methods, %s, in an abstract module, %s. " + + "Did you forget to add a Dagger binding annotation (e.g. @Binds)?", + abstractMethodsWithMissingBinds.stream() + .map(XElements::toStableString) + .collect(DaggerStreams.toImmutableList()), + XElements.toStableString(module)); + + ImmutableList replacedModules = ImmutableList.of(); + if (module.hasAnnotation(ClassNames.TEST_INSTALL_IN)) { + Optional originatingTestElement = Processors.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", + originatingTestElement.map(XTypeElement::getQualifiedName).orElse("")); + + XAnnotation testInstallIn = module.getAnnotation(ClassNames.TEST_INSTALL_IN); + replacedModules = Processors.getAnnotationClassValues(testInstallIn, "replaces"); + + ProcessorErrors.checkState( + !replacedModules.isEmpty(), + // TODO(b/152801981): this should really error on the annotation value + module, + "@TestInstallIn#replaces() cannot be empty. Use @InstallIn instead."); + + ImmutableList nonInstallInModules = + replacedModules.stream() + .filter(replacedModule -> !replacedModule.hasAnnotation(ClassNames.INSTALL_IN)) + .collect(toImmutableList()); + + ProcessorErrors.checkState( + nonInstallInModules.isEmpty(), + // TODO(b/152801981): this should really error on the annotation value + module, + "@TestInstallIn#replaces() can only contain @InstallIn modules, but found: %s", + nonInstallInModules.stream() + .map(XElements::toStableString) + .collect(DaggerStreams.toImmutableList())); + + ImmutableList hiltWrapperModules = + replacedModules.stream() + .filter( + replacedModule -> + replacedModule.getClassName().simpleName().startsWith("HiltWrapper_")) + .collect(toImmutableList()); + + ProcessorErrors.checkState( + hiltWrapperModules.isEmpty(), + // TODO(b/152801981): this should really error on the annotation value + module, + "@TestInstallIn#replaces() cannot contain Hilt generated public wrapper modules, " + + "but found: %s. ", + hiltWrapperModules.stream() + .map(XElements::toStableString) + .collect(DaggerStreams.toImmutableList())); + + if (!module.getPackageName().startsWith("dagger.hilt")) { + // Prevent external users from overriding Hilt's internal modules. Techincally, except for + // ApplicationContextModule, making all modules pkg-private should be enough but this is an + // extra measure of precaution. + ImmutableList hiltInternalModules = + replacedModules.stream() + .filter(replacedModule -> replacedModule.getPackageName().startsWith("dagger.hilt")) + .collect(toImmutableList()); + + ProcessorErrors.checkState( + hiltInternalModules.isEmpty(), + // TODO(b/152801981): this should really error on the annotation value + module, + "@TestInstallIn#replaces() cannot contain internal Hilt modules, but found: %s. ", + hiltInternalModules.stream() + .map(XElements::toStableString) + .collect(DaggerStreams.toImmutableList())); + } + + // Prevent users from uninstalling test-specific @InstallIn modules. + ImmutableList replacedTestSpecificInstallIn = + replacedModules.stream() + .filter( + replacedModule -> + Processors.getOriginatingTestElement(replacedModule).isPresent()) + .collect(toImmutableList()); + + ProcessorErrors.checkState( + replacedTestSpecificInstallIn.isEmpty(), + // TODO(b/152801981): this should really error on the annotation value + module, + "@TestInstallIn#replaces() cannot replace test specific @InstallIn modules, but found: " + + "%s. Please remove the @InstallIn module manually rather than replacing it.", + replacedTestSpecificInstallIn.stream() + .map(XElements::toStableString) + .collect(DaggerStreams.toImmutableList())); + } + + generateAggregatedDeps( + "modules", + module, + moduleAnnotation, + replacedModules.stream().map(XTypeElement::getClassName).collect(toImmutableSet())); + } + + private void processEntryPoint( + XElement element, Optional installInAnnotation, ClassName entryPointAnnotation) + throws Exception { + ProcessorErrors.checkState( + installInAnnotation.isPresent() , + element, + "@%s %s must also be annotated with @InstallIn", + entryPointAnnotation.simpleName(), + XElements.toStableString(element)); + + ProcessorErrors.checkState( + !element.hasAnnotation(ClassNames.TEST_INSTALL_IN), + element, + "@TestInstallIn can only be used with modules"); + + ProcessorErrors.checkState( + XElementKt.isTypeElement(element) && XElements.asTypeElement(element).isInterface(), + element, + "Only interfaces can be annotated with @%s: %s", + entryPointAnnotation.simpleName(), + XElements.toStableString(element)); + XTypeElement entryPoint = XElements.asTypeElement(element); + + if (entryPointAnnotation.equals(ClassNames.EARLY_ENTRY_POINT)) { + ImmutableSet components = Components.getComponents(element); + ProcessorErrors.checkState( + components.equals(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)), + element, + "@EarlyEntryPoint can only be installed into the SingletonComponent. Found: %s", + components); + + Optional optionalTestElement = Processors.getOriginatingTestElement(element); + 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.", + entryPoint.getQualifiedName(), + optionalTestElement.map(testElement -> testElement.getQualifiedName()).orElse("")); + } + + generateAggregatedDeps( + entryPointAnnotation.equals(ClassNames.COMPONENT_ENTRY_POINT) + ? "componentEntryPoints" + : "entryPoints", + entryPoint, + entryPointAnnotation, + ImmutableSet.of()); + } + + private void generateAggregatedDeps( + String key, + XTypeElement element, + ClassName annotation, + ImmutableSet replacedModules) + throws Exception { + // Get @InstallIn components here to catch errors before skipping user's pkg-private element. + ImmutableSet components = Components.getComponents(element); + + if (isValidKind(element)) { + Optional pkgPrivateMetadata = PkgPrivateMetadata.of(element, annotation); + if (pkgPrivateMetadata.isPresent()) { + if (key.contentEquals("modules")) { + new PkgPrivateModuleGenerator(processingEnv(), pkgPrivateMetadata.get()).generate(); + } else { + new PkgPrivateEntryPointGenerator(processingEnv(), pkgPrivateMetadata.get()).generate(); + } + } else { + Optional testName = + Processors.getOriginatingTestElement(element).map(XTypeElement::getClassName); + new AggregatedDepsGenerator(key, element, testName, components, replacedModules).generate(); + } + } + } + + private static Optional getAnnotation( + XElement element, ImmutableSet annotations) { + ImmutableSet usedAnnotations = + annotations.stream().filter(element::hasAnnotation).collect(toImmutableSet()); + + if (usedAnnotations.isEmpty()) { + return Optional.empty(); + } + + ProcessorErrors.checkState( + usedAnnotations.size() == 1, + element, + "Only one of the following annotations can be used on %s: %s", + XElements.toStableString(element), + usedAnnotations); + + return Optional.of(getOnlyElement(usedAnnotations)); + } + + private static boolean isValidKind(XElement 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. + return !XElements.asTypeElement(element).getType().isError(); + } + + private boolean installInCheckDisabled(XElement element) { + return isModuleInstallInCheckDisabled(processingEnv()) + || element.hasAnnotation(ClassNames.DISABLE_INSTALL_IN_CHECK); + } + + /** + * When using Dagger Producers, don't process generated modules. They will not have the expected + * annotations. + */ + private static boolean isDaggerGeneratedModule(XElement element) { + if (!element.hasAnnotation(ClassNames.MODULE)) { + return false; + } + return element.getAllAnnotations().stream() + .filter(annotation -> isGenerated(annotation)) + .map(annotation -> getOnlyElement(annotation.getAsStringList("value"))) + .anyMatch(value -> value.startsWith("dagger")); + } + + private static boolean isGenerated(XAnnotation annotation) { + String name = annotation.getTypeElement().getQualifiedName(); + + return name.equals("javax.annotation.Generated") + || name.equals("javax.annotation.processing.Generated"); + } +} diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java index 4f034e1ebc1..73cbc59eb09 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java @@ -16,395 +16,20 @@ package dagger.hilt.processor.internal.aggregateddeps; -import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.hilt.processor.internal.HiltCompilerOptions.isModuleInstallInCheckDisabled; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; -import androidx.room.compiler.processing.XAnnotation; -import androidx.room.compiler.processing.XElement; -import androidx.room.compiler.processing.XElementKt; -import androidx.room.compiler.processing.XExecutableElement; -import androidx.room.compiler.processing.XMethodElement; -import androidx.room.compiler.processing.XTypeElement; import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -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.ProcessorErrors; -import dagger.hilt.processor.internal.Processors; -import dagger.internal.codegen.extension.DaggerStreams; -import dagger.internal.codegen.xprocessing.XElements; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; +import dagger.hilt.processor.internal.BaseProcessingStep; +import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor; import javax.annotation.processing.Processor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; /** Processor that outputs dummy files to propagate information through multiple javac runs. */ @IncrementalAnnotationProcessor(ISOLATING) @AutoService(Processor.class) -public final class AggregatedDepsProcessor extends BaseProcessor { - - private static final ImmutableSet ENTRY_POINT_ANNOTATIONS = - ImmutableSet.of( - ClassNames.ENTRY_POINT, - ClassNames.EARLY_ENTRY_POINT, - ClassNames.GENERATED_ENTRY_POINT, - ClassNames.COMPONENT_ENTRY_POINT); - - private static final ImmutableSet MODULE_ANNOTATIONS = - ImmutableSet.of( - ClassNames.MODULE); - - private static final ImmutableSet INSTALL_IN_ANNOTATIONS = - ImmutableSet.of(ClassNames.INSTALL_IN, ClassNames.TEST_INSTALL_IN); - - private final Set seen = new HashSet<>(); - - @Override - public Set getSupportedAnnotationTypes() { - return ImmutableSet.builder() - .addAll(INSTALL_IN_ANNOTATIONS) - .addAll(MODULE_ANNOTATIONS) - .addAll(ENTRY_POINT_ANNOTATIONS) - .build() - .stream() - .map(Object::toString) - .collect(toImmutableSet()); - } - +public final class AggregatedDepsProcessor extends JavacBaseProcessingStepProcessor { @Override - public void processEach(XTypeElement annotation, XElement element) throws Exception { - if (!seen.add(element)) { - return; - } - - Optional installInAnnotation = getAnnotation(element, INSTALL_IN_ANNOTATIONS); - Optional entryPointAnnotation = getAnnotation(element, ENTRY_POINT_ANNOTATIONS); - Optional moduleAnnotation = getAnnotation(element, MODULE_ANNOTATIONS); - - boolean hasInstallIn = installInAnnotation.isPresent(); - boolean isEntryPoint = entryPointAnnotation.isPresent(); - boolean isModule = moduleAnnotation.isPresent(); - - ProcessorErrors.checkState( - !hasInstallIn || isEntryPoint || isModule, - element, - "@%s-annotated classes must also be annotated with @Module or @EntryPoint: %s", - installInAnnotation.map(ClassName::simpleName).orElse("@InstallIn"), - XElements.toStableString(element)); - - ProcessorErrors.checkState( - !(isEntryPoint && isModule), - element, - "@%s and @%s cannot be used on the same interface: %s", - moduleAnnotation.map(ClassName::simpleName).orElse("@Module"), - entryPointAnnotation.map(ClassName::simpleName).orElse("@EntryPoint"), - XElements.toStableString(element)); - - if (isModule) { - processModule(element, installInAnnotation, moduleAnnotation.get()); - } else if (isEntryPoint) { - processEntryPoint(element, installInAnnotation, entryPointAnnotation.get()); - } else { - throw new AssertionError(); - } - } - - private void processModule( - XElement element, Optional installInAnnotation, ClassName moduleAnnotation) - throws Exception { - ProcessorErrors.checkState( - installInAnnotation.isPresent() - || isDaggerGeneratedModule(element) - || installInCheckDisabled(element), - element, - "%s is missing an @InstallIn annotation. If this was intentional, see" - + " https://dagger.dev/hilt/flags#disable-install-in-check for how to disable this" - + " check.", - XElements.toStableString(element)); - - if (!installInAnnotation.isPresent()) { - // Modules without @InstallIn or @TestInstallIn annotations don't need to be processed further - return; - } - - ProcessorErrors.checkState( - XElementKt.isTypeElement(element), - element, - "Only classes and interfaces can be annotated with @Module: %s", - XElements.toStableString(element)); - - XTypeElement module = XElements.asTypeElement(element); - - ProcessorErrors.checkState( - module.isClass() || module.isInterface() || module.isKotlinObject(), - module, - "Only classes and interfaces can be annotated with @Module: %s", - XElements.toStableString(module)); - - ProcessorErrors.checkState( - Processors.isTopLevel(module) - || module.isStatic() - || module.isAbstract() - || module.getEnclosingElement().hasAnnotation(ClassNames.HILT_ANDROID_TEST), - module, - "Nested @%s modules must be static unless they are directly nested within a test. " - + "Found: %s", - installInAnnotation.get().simpleName(), - XElements.toStableString(module)); - - // 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(module.getClassName()) - || !Processors.requiresModuleInstance(module) - || Processors.hasVisibleEmptyConstructor(module), - module, - "Modules that need to be instantiated by Hilt must have a visible, empty constructor."); - - // TODO(b/28989613): This should really be fixed in Dagger. Remove once Dagger bug is fixed. - ImmutableList abstractMethodsWithMissingBinds = - module.getDeclaredMethods().stream() - .filter(XMethodElement::isAbstract) - .filter(method -> !Processors.hasDaggerAbstractMethodAnnotation(method)) - .collect(toImmutableList()); - ProcessorErrors.checkState( - abstractMethodsWithMissingBinds.isEmpty(), - module, - "Found unimplemented abstract methods, %s, in an abstract module, %s. " - + "Did you forget to add a Dagger binding annotation (e.g. @Binds)?", - abstractMethodsWithMissingBinds.stream() - .map(XElements::toStableString) - .collect(DaggerStreams.toImmutableList()), - XElements.toStableString(module)); - - ImmutableList replacedModules = ImmutableList.of(); - if (module.hasAnnotation(ClassNames.TEST_INSTALL_IN)) { - Optional originatingTestElement = Processors.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", - originatingTestElement.map(XTypeElement::getQualifiedName).orElse("")); - - XAnnotation testInstallIn = module.getAnnotation(ClassNames.TEST_INSTALL_IN); - replacedModules = Processors.getAnnotationClassValues(testInstallIn, "replaces"); - - ProcessorErrors.checkState( - !replacedModules.isEmpty(), - // TODO(b/152801981): this should really error on the annotation value - module, - "@TestInstallIn#replaces() cannot be empty. Use @InstallIn instead."); - - ImmutableList nonInstallInModules = - replacedModules.stream() - .filter(replacedModule -> !replacedModule.hasAnnotation(ClassNames.INSTALL_IN)) - .collect(toImmutableList()); - - ProcessorErrors.checkState( - nonInstallInModules.isEmpty(), - // TODO(b/152801981): this should really error on the annotation value - module, - "@TestInstallIn#replaces() can only contain @InstallIn modules, but found: %s", - nonInstallInModules.stream() - .map(XElements::toStableString) - .collect(DaggerStreams.toImmutableList())); - - ImmutableList hiltWrapperModules = - replacedModules.stream() - .filter( - replacedModule -> - replacedModule.getClassName().simpleName().startsWith("HiltWrapper_")) - .collect(toImmutableList()); - - ProcessorErrors.checkState( - hiltWrapperModules.isEmpty(), - // TODO(b/152801981): this should really error on the annotation value - module, - "@TestInstallIn#replaces() cannot contain Hilt generated public wrapper modules, " - + "but found: %s. ", - hiltWrapperModules.stream() - .map(XElements::toStableString) - .collect(DaggerStreams.toImmutableList())); - - if (!module.getPackageName().startsWith("dagger.hilt")) { - // Prevent external users from overriding Hilt's internal modules. Techincally, except for - // ApplicationContextModule, making all modules pkg-private should be enough but this is an - // extra measure of precaution. - ImmutableList hiltInternalModules = - replacedModules.stream() - .filter(replacedModule -> replacedModule.getPackageName().startsWith("dagger.hilt")) - .collect(toImmutableList()); - - ProcessorErrors.checkState( - hiltInternalModules.isEmpty(), - // TODO(b/152801981): this should really error on the annotation value - module, - "@TestInstallIn#replaces() cannot contain internal Hilt modules, but found: %s. ", - hiltInternalModules.stream() - .map(XElements::toStableString) - .collect(DaggerStreams.toImmutableList())); - } - - // Prevent users from uninstalling test-specific @InstallIn modules. - ImmutableList replacedTestSpecificInstallIn = - replacedModules.stream() - .filter( - replacedModule -> - Processors.getOriginatingTestElement(replacedModule).isPresent()) - .collect(toImmutableList()); - - ProcessorErrors.checkState( - replacedTestSpecificInstallIn.isEmpty(), - // TODO(b/152801981): this should really error on the annotation value - module, - "@TestInstallIn#replaces() cannot replace test specific @InstallIn modules, but found: " - + "%s. Please remove the @InstallIn module manually rather than replacing it.", - replacedTestSpecificInstallIn.stream() - .map(XElements::toStableString) - .collect(DaggerStreams.toImmutableList())); - } - - generateAggregatedDeps( - "modules", - module, - moduleAnnotation, - replacedModules.stream().map(XTypeElement::getClassName).collect(toImmutableSet())); - } - - private void processEntryPoint( - XElement element, Optional installInAnnotation, ClassName entryPointAnnotation) - throws Exception { - ProcessorErrors.checkState( - installInAnnotation.isPresent() , - element, - "@%s %s must also be annotated with @InstallIn", - entryPointAnnotation.simpleName(), - XElements.toStableString(element)); - - ProcessorErrors.checkState( - !element.hasAnnotation(ClassNames.TEST_INSTALL_IN), - element, - "@TestInstallIn can only be used with modules"); - - ProcessorErrors.checkState( - XElementKt.isTypeElement(element) && XElements.asTypeElement(element).isInterface(), - element, - "Only interfaces can be annotated with @%s: %s", - entryPointAnnotation.simpleName(), - XElements.toStableString(element)); - XTypeElement entryPoint = XElements.asTypeElement(element); - - if (entryPointAnnotation.equals(ClassNames.EARLY_ENTRY_POINT)) { - ImmutableSet components = Components.getComponents(element); - ProcessorErrors.checkState( - components.equals(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)), - element, - "@EarlyEntryPoint can only be installed into the SingletonComponent. Found: %s", - components); - - Optional optionalTestElement = Processors.getOriginatingTestElement(element); - 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.", - entryPoint.getQualifiedName(), - optionalTestElement.map(testElement -> testElement.getQualifiedName()).orElse("")); - } - - generateAggregatedDeps( - entryPointAnnotation.equals(ClassNames.COMPONENT_ENTRY_POINT) - ? "componentEntryPoints" - : "entryPoints", - entryPoint, - entryPointAnnotation, - ImmutableSet.of()); - } - - private void generateAggregatedDeps( - String key, - XTypeElement element, - ClassName annotation, - ImmutableSet replacedModules) - throws Exception { - // Get @InstallIn components here to catch errors before skipping user's pkg-private element. - ImmutableSet components = Components.getComponents(element); - - if (isValidKind(element)) { - Optional pkgPrivateMetadata = PkgPrivateMetadata.of(element, annotation); - if (pkgPrivateMetadata.isPresent()) { - if (key.contentEquals("modules")) { - new PkgPrivateModuleGenerator(processingEnv(), pkgPrivateMetadata.get()).generate(); - } else { - new PkgPrivateEntryPointGenerator(processingEnv(), pkgPrivateMetadata.get()).generate(); - } - } else { - Optional testName = - Processors.getOriginatingTestElement(element).map(XTypeElement::getClassName); - new AggregatedDepsGenerator(key, element, testName, components, replacedModules).generate(); - } - } - } - - private static Optional getAnnotation( - XElement element, ImmutableSet annotations) { - ImmutableSet usedAnnotations = - annotations.stream().filter(element::hasAnnotation).collect(toImmutableSet()); - - if (usedAnnotations.isEmpty()) { - return Optional.empty(); - } - - ProcessorErrors.checkState( - usedAnnotations.size() == 1, - element, - "Only one of the following annotations can be used on %s: %s", - XElements.toStableString(element), - usedAnnotations); - - return Optional.of(getOnlyElement(usedAnnotations)); - } - - private static boolean isValidKind(XElement 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. - return !XElements.asTypeElement(element).getType().isError(); - } - - private boolean installInCheckDisabled(XElement element) { - return isModuleInstallInCheckDisabled(processingEnv()) - || element.hasAnnotation(ClassNames.DISABLE_INSTALL_IN_CHECK); - } - - /** - * When using Dagger Producers, don't process generated modules. They will not have the expected - * annotations. - */ - private static boolean isDaggerGeneratedModule(XElement element) { - if (!element.hasAnnotation(ClassNames.MODULE)) { - return false; - } - return element.getAllAnnotations().stream() - .filter(annotation -> isGenerated(annotation)) - .map(annotation -> getOnlyElement(annotation.getAsStringList("value"))) - .anyMatch(value -> value.startsWith("dagger")); - } - - private static boolean isGenerated(XAnnotation annotation) { - String name = annotation.getTypeElement().getQualifiedName(); - - return name.equals("javax.annotation.Generated") - || name.equals("javax.annotation.processing.Generated"); + protected BaseProcessingStep processingStep() { + return new AggregatedDepsProcessingStep(getXProcessingEnv()); } } diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD index aa5d757b851..b5ced4cdfde 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD +++ b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD @@ -37,6 +37,7 @@ java_library( name = "processor_lib", srcs = [ "AggregatedDepsGenerator.java", + "AggregatedDepsProcessingStep.java", "AggregatedDepsProcessor.java", "PkgPrivateEntryPointGenerator.java", "PkgPrivateModuleGenerator.java", @@ -51,7 +52,6 @@ java_library( "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/xprocessing", - "//third_party/java/auto:common", "//third_party/java/auto:service", "//third_party/java/guava/collect", "//third_party/java/incap", diff --git a/java/dagger/hilt/processor/internal/definecomponent/BUILD b/java/dagger/hilt/processor/internal/definecomponent/BUILD index cac99e746ad..cc8d519589d 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/BUILD +++ b/java/dagger/hilt/processor/internal/definecomponent/BUILD @@ -30,6 +30,7 @@ java_plugin( java_library( name = "processor_lib", srcs = [ + "DefineComponentProcessingStep.java", "DefineComponentProcessor.java", ], deps = [ diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessingStep.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessingStep.java new file mode 100644 index 00000000000..fe212278442 --- /dev/null +++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessingStep.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019 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.definecomponent; + + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XRoundEnv; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import dagger.hilt.processor.internal.BaseProcessingStep; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.definecomponent.DefineComponentBuilderMetadatas.DefineComponentBuilderMetadata; +import dagger.hilt.processor.internal.definecomponent.DefineComponentMetadatas.DefineComponentMetadata; + +/** + * A processor for {@link dagger.hilt.DefineComponent} and {@link + * dagger.hilt.DefineComponent.Builder}. + */ +public final class DefineComponentProcessingStep extends BaseProcessingStep { + // Note: these caches should be cleared between rounds. + private DefineComponentMetadatas componentMetadatas; + private DefineComponentBuilderMetadatas componentBuilderMetadatas; + + public DefineComponentProcessingStep(XProcessingEnv env) { + super(env); + } + + @Override + public void preProcess(XProcessingEnv env, XRoundEnv round) { + componentMetadatas = DefineComponentMetadatas.create(); + componentBuilderMetadatas = DefineComponentBuilderMetadatas.create(componentMetadatas); + } + + @Override + public void postProcess(XProcessingEnv env, XRoundEnv round) { + componentMetadatas = null; + componentBuilderMetadatas = null; + } + + @Override + protected ImmutableSet annotationClassNames() { + return ImmutableSet.of(ClassNames.DEFINE_COMPONENT, ClassNames.DEFINE_COMPONENT_BUILDER); + } + + @Override + public void processEach(ClassName annotation, XElement element) { + if (annotation.equals(ClassNames.DEFINE_COMPONENT)) { + // TODO(bcorso): For cycles we currently process each element in the cycle. We should skip + // processing of subsequent elements in a cycle, but this requires ensuring that the first + // element processed is always the same so that our failure tests are stable. + DefineComponentMetadata metadata = componentMetadatas.get(element); + generateFile("component", metadata.component()); + } else if (annotation.equals(ClassNames.DEFINE_COMPONENT_BUILDER)) { + DefineComponentBuilderMetadata metadata = componentBuilderMetadatas.get(element); + generateFile("builder", metadata.builder()); + } else { + throw new AssertionError("Unhandled annotation type: " + annotation.canonicalName()); + } + } + + private void generateFile(String member, XTypeElement typeElement) { + Processors.generateAggregatingClass( + ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE, + AnnotationSpec.builder(ClassNames.DEFINE_COMPONENT_CLASSES) + .addMember(member, "$S", typeElement.getQualifiedName()) + .build(), + typeElement, + getClass()); + } +} diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java index bd907b63b35..81bc43a52e3 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java +++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java @@ -18,17 +18,9 @@ import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; -import androidx.room.compiler.processing.XElement; -import androidx.room.compiler.processing.XRoundEnv; -import androidx.room.compiler.processing.XTypeElement; import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableSet; -import com.squareup.javapoet.AnnotationSpec; -import dagger.hilt.processor.internal.BaseProcessor; -import dagger.hilt.processor.internal.ClassNames; -import dagger.hilt.processor.internal.Processors; -import dagger.hilt.processor.internal.definecomponent.DefineComponentBuilderMetadatas.DefineComponentBuilderMetadata; -import dagger.hilt.processor.internal.definecomponent.DefineComponentMetadatas.DefineComponentMetadata; +import dagger.hilt.processor.internal.BaseProcessingStep; +import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor; import javax.annotation.processing.Processor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; @@ -38,52 +30,9 @@ */ @IncrementalAnnotationProcessor(ISOLATING) @AutoService(Processor.class) -public final class DefineComponentProcessor extends BaseProcessor { - // Note: these caches should be cleared between rounds. - private DefineComponentMetadatas componentMetadatas; - private DefineComponentBuilderMetadatas componentBuilderMetadatas; - - @Override - public void preRoundProcess(XRoundEnv roundEnv) { - componentMetadatas = DefineComponentMetadatas.create(); - componentBuilderMetadatas = DefineComponentBuilderMetadatas.create(componentMetadatas); - } - - @Override - public void postRoundProcess(XRoundEnv roundEnv) { - componentMetadatas = null; - componentBuilderMetadatas = null; - } - - @Override - public ImmutableSet getSupportedAnnotationTypes() { - return ImmutableSet.of( - ClassNames.DEFINE_COMPONENT.toString(), ClassNames.DEFINE_COMPONENT_BUILDER.toString()); - } - +public final class DefineComponentProcessor extends JavacBaseProcessingStepProcessor { @Override - public void processEach(XTypeElement annotation, XElement element) throws Exception { - if (annotation.getClassName().equals(ClassNames.DEFINE_COMPONENT)) { - // TODO(bcorso): For cycles we currently process each element in the cycle. We should skip - // processing of subsequent elements in a cycle, but this requires ensuring that the first - // element processed is always the same so that our failure tests are stable. - DefineComponentMetadata metadata = componentMetadatas.get(element); - generateFile("component", metadata.component()); - } else if (annotation.getClassName().equals(ClassNames.DEFINE_COMPONENT_BUILDER)) { - DefineComponentBuilderMetadata metadata = componentBuilderMetadatas.get(element); - generateFile("builder", metadata.builder()); - } else { - throw new AssertionError("Unhandled annotation type: " + annotation.getQualifiedName()); - } - } - - private void generateFile(String member, XTypeElement typeElement) { - Processors.generateAggregatingClass( - ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE, - AnnotationSpec.builder(ClassNames.DEFINE_COMPONENT_CLASSES) - .addMember(member, "$S", typeElement.getQualifiedName()) - .build(), - typeElement, - getClass()); + protected BaseProcessingStep processingStep() { + return new DefineComponentProcessingStep(getXProcessingEnv()); } } diff --git a/java/dagger/hilt/processor/internal/root/BUILD b/java/dagger/hilt/processor/internal/root/BUILD index efd6cd62dc2..865b7144a60 100644 --- a/java/dagger/hilt/processor/internal/root/BUILD +++ b/java/dagger/hilt/processor/internal/root/BUILD @@ -30,6 +30,7 @@ java_library( name = "component_tree_deps_processor_lib", srcs = [ "ComponentGenerator.java", + "ComponentTreeDepsProcessingStep.java", "ComponentTreeDepsProcessor.java", "EarlySingletonComponentCreatorGenerator.java", "RootFileFormatter.java", @@ -54,7 +55,6 @@ java_library( "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/xprocessing", - "//third_party/java/auto:common", "//third_party/java/auto:service", "//third_party/java/error_prone:annotations", "//third_party/java/guava/base", diff --git a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessingStep.java b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessingStep.java new file mode 100644 index 00000000000..295a7794b9d --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessingStep.java @@ -0,0 +1,193 @@ +/* + * 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.root; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XRoundEnv; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata; +import dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator; +import dagger.hilt.processor.internal.BaseProcessingStep; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.ComponentDescriptor; +import dagger.hilt.processor.internal.ComponentNames; +import dagger.hilt.processor.internal.ProcessorErrors; +import dagger.hilt.processor.internal.Processors; +import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata; +import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; +import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata; +import dagger.hilt.processor.internal.aliasof.AliasOfs; +import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata; +import dagger.hilt.processor.internal.definecomponent.DefineComponents; +import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata; +import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata; +import dagger.internal.codegen.xprocessing.XElements; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +/** Processor that outputs dagger components based on transitive build deps. */ +public final class ComponentTreeDepsProcessingStep extends BaseProcessingStep { + private final Set componentTreeDepNames = new HashSet<>(); + private final Set processed = new HashSet<>(); + + public ComponentTreeDepsProcessingStep(XProcessingEnv env) { + super(env); + } + + @Override + protected ImmutableSet annotationClassNames() { + return ImmutableSet.of(ClassNames.COMPONENT_TREE_DEPS); + } + + @Override + protected void processEach(ClassName annotation, XElement element) { + componentTreeDepNames.add(XElements.asTypeElement(element).getClassName()); + } + + @Override + public void postProcess(XProcessingEnv env, XRoundEnv roundEnv) throws Exception { + ImmutableSet componentTreeDepsToProcess = + componentTreeDepNames.stream() + .filter(className -> !processed.contains(className)) + .map(className -> processingEnv().requireTypeElement(className)) + .map(element -> ComponentTreeDepsMetadata.from(element, processingEnv())) + .collect(toImmutableSet()); + + DefineComponents defineComponents = DefineComponents.create(); + for (ComponentTreeDepsMetadata metadata : componentTreeDepsToProcess) { + processComponentTreeDeps(metadata, defineComponents); + } + } + + private void processComponentTreeDeps( + ComponentTreeDepsMetadata metadata, DefineComponents defineComponents) throws IOException { + XTypeElement metadataElement = processingEnv().requireTypeElement(metadata.name()); + try { + // We choose a name for the generated components/wrapper based off of the originating element + // annotated with @ComponentTreeDeps. This is close to but isn't necessarily a "real" name of + // a root, since with shared test components, even for single roots, the component tree deps + // will be moved to a shared package with a deduped name. + ClassName renamedRoot = Processors.removeNameSuffix(metadataElement, "_ComponentTreeDeps"); + ComponentNames componentNames = ComponentNames.withRenaming(rootName -> renamedRoot); + + boolean isDefaultRoot = ClassNames.DEFAULT_ROOT.equals(renamedRoot); + ImmutableSet roots = + AggregatedRootMetadata.from(metadata.aggregatedRootDeps(), processingEnv()).stream() + .map(AggregatedRootMetadata::rootElement) + .map(rootElement -> Root.create(rootElement, processingEnv())) + .collect(toImmutableSet()); + + // TODO(bcorso): For legacy reasons, a lot of the generating code requires a "root" as input + // since we used to assume 1 root per component tree. Now that each ComponentTreeDeps may + // represent multiple roots, we should refactor this logic. + Root root = + isDefaultRoot + ? Root.createDefaultRoot(processingEnv()) + // Non-default roots should only ever be associated with one root element + : getOnlyElement(roots); + + ImmutableSet componentDescriptors = + defineComponents.getComponentDescriptors( + DefineComponentClassesMetadata.from(metadata.defineComponentDeps())); + ComponentTree tree = ComponentTree.from(componentDescriptors); + ComponentDependencies deps = + ComponentDependencies.from( + componentDescriptors, + AggregatedDepsMetadata.from(metadata.aggregatedDeps()), + AggregatedUninstallModulesMetadata.from(metadata.aggregatedUninstallModulesDeps()), + AggregatedEarlyEntryPointMetadata.from(metadata.aggregatedEarlyEntryPointDeps()), + processingEnv()); + AliasOfs aliasOfs = + AliasOfs.create( + AliasOfPropagatedDataMetadata.from(metadata.aliasOfDeps()), componentDescriptors); + RootMetadata rootMetadata = RootMetadata.create(root, tree, deps, aliasOfs, processingEnv()); + + generateComponents(metadata, rootMetadata, componentNames); + + // Generate a creator for the early entry point if there is a default component available + // and there are early entry points. + if (isDefaultRoot && !metadata.aggregatedEarlyEntryPointDeps().isEmpty()) { + EarlySingletonComponentCreatorGenerator.generate(processingEnv()); + } + + if (root.isTestRoot()) { + // Generate test related classes for each test root that uses this component. + ImmutableList rootMetadatas = + roots.stream() + .map(test -> RootMetadata.create(test, tree, deps, aliasOfs, processingEnv())) + .collect(toImmutableList()); + generateTestComponentData(metadataElement, rootMetadatas, componentNames); + } else { + generateApplication(root.element()); + } + + setProcessingState(metadata, root); + } catch (Exception e) { + processed.add(metadata.name()); + throw e; + } + } + + private void setProcessingState(ComponentTreeDepsMetadata metadata, Root root) { + processed.add(metadata.name()); + } + + private void generateComponents( + ComponentTreeDepsMetadata metadata, RootMetadata rootMetadata, ComponentNames componentNames) + throws IOException { + RootGenerator.generate(metadata, rootMetadata, componentNames, processingEnv()); + } + + private void generateTestComponentData( + XTypeElement metadataElement, + ImmutableList rootMetadatas, + ComponentNames componentNames) + throws IOException { + for (RootMetadata rootMetadata : rootMetadatas) { + // TODO(bcorso): Consider moving this check earlier into processEach. + XTypeElement testElement = rootMetadata.testRootMetadata().testElement(); + ProcessorErrors.checkState( + testElement.isPublic(), + testElement, + "Hilt tests must be public, but found: %s", + XElements.toStableString(testElement)); + new TestComponentDataGenerator(processingEnv(), metadataElement, rootMetadata, componentNames) + .generate(); + } + } + + private void generateApplication(XTypeElement rootElement) throws IOException { + // The generated application references the generated component so they must be generated + // in the same build unit. Thus, we only generate the application here if we're using the + // Hilt Gradle plugin's aggregating task. If we're using the aggregating processor, we need + // to generate the application within AndroidEntryPointProcessor instead. + if (!useAggregatingRootProcessor(processingEnv())) { + AndroidEntryPointMetadata metadata = AndroidEntryPointMetadata.of(rootElement); + new ApplicationGenerator(processingEnv(), metadata).generate(); + } + } +} diff --git a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java index a7ff4b5163a..08c0e8b09a4 100644 --- a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java +++ b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java @@ -16,182 +16,20 @@ package dagger.hilt.processor.internal.root; -import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; -import androidx.room.compiler.processing.XElement; -import androidx.room.compiler.processing.XRoundEnv; -import androidx.room.compiler.processing.XTypeElement; import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.squareup.javapoet.ClassName; -import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata; -import dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator; -import dagger.hilt.processor.internal.BaseProcessor; -import dagger.hilt.processor.internal.ClassNames; -import dagger.hilt.processor.internal.ComponentDescriptor; -import dagger.hilt.processor.internal.ComponentNames; -import dagger.hilt.processor.internal.ProcessorErrors; -import dagger.hilt.processor.internal.Processors; -import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata; -import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; -import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata; -import dagger.hilt.processor.internal.aliasof.AliasOfs; -import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata; -import dagger.hilt.processor.internal.definecomponent.DefineComponents; -import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata; -import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata; -import dagger.internal.codegen.xprocessing.XElements; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; +import dagger.hilt.processor.internal.BaseProcessingStep; +import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor; import javax.annotation.processing.Processor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; /** Processor that outputs dagger components based on transitive build deps. */ @IncrementalAnnotationProcessor(ISOLATING) @AutoService(Processor.class) -public final class ComponentTreeDepsProcessor extends BaseProcessor { - private final Set componentTreeDepNames = new HashSet<>(); - private final Set processed = new HashSet<>(); - - @Override - public ImmutableSet getSupportedAnnotationTypes() { - return ImmutableSet.of(ClassNames.COMPONENT_TREE_DEPS.toString()); - } - - @Override - public void processEach(XTypeElement annotation, XElement element) { - componentTreeDepNames.add(XElements.asTypeElement(element).getClassName()); - } - +public final class ComponentTreeDepsProcessor extends JavacBaseProcessingStepProcessor { @Override - public void postRoundProcess(XRoundEnv roundEnv) throws Exception { - ImmutableSet componentTreeDepsToProcess = - componentTreeDepNames.stream() - .filter(className -> !processed.contains(className)) - .map(className -> processingEnv().requireTypeElement(className)) - .map(element -> ComponentTreeDepsMetadata.from(element, processingEnv())) - .collect(toImmutableSet()); - - DefineComponents defineComponents = DefineComponents.create(); - for (ComponentTreeDepsMetadata metadata : componentTreeDepsToProcess) { - processComponentTreeDeps(metadata, defineComponents); - } - } - - private void processComponentTreeDeps( - ComponentTreeDepsMetadata metadata, - DefineComponents defineComponents) throws IOException { - XTypeElement metadataElement = processingEnv().requireTypeElement(metadata.name()); - try { - // We choose a name for the generated components/wrapper based off of the originating element - // annotated with @ComponentTreeDeps. This is close to but isn't necessarily a "real" name of - // a root, since with shared test components, even for single roots, the component tree deps - // will be moved to a shared package with a deduped name. - ClassName renamedRoot = Processors.removeNameSuffix(metadataElement, "_ComponentTreeDeps"); - ComponentNames componentNames = ComponentNames.withRenaming(rootName -> renamedRoot); - - boolean isDefaultRoot = ClassNames.DEFAULT_ROOT.equals(renamedRoot); - ImmutableSet roots = - AggregatedRootMetadata.from(metadata.aggregatedRootDeps(), processingEnv()).stream() - .map(AggregatedRootMetadata::rootElement) - .map(rootElement -> Root.create(rootElement, processingEnv())) - .collect(toImmutableSet()); - - // TODO(bcorso): For legacy reasons, a lot of the generating code requires a "root" as input - // since we used to assume 1 root per component tree. Now that each ComponentTreeDeps may - // represent multiple roots, we should refactor this logic. - Root root = - isDefaultRoot - ? Root.createDefaultRoot(processingEnv()) - // Non-default roots should only ever be associated with one root element - : getOnlyElement(roots); - - ImmutableSet componentDescriptors = - defineComponents.getComponentDescriptors( - DefineComponentClassesMetadata.from(metadata.defineComponentDeps())); - ComponentTree tree = ComponentTree.from(componentDescriptors); - ComponentDependencies deps = - ComponentDependencies.from( - componentDescriptors, - AggregatedDepsMetadata.from(metadata.aggregatedDeps()), - AggregatedUninstallModulesMetadata.from( - metadata.aggregatedUninstallModulesDeps()), - AggregatedEarlyEntryPointMetadata.from( - metadata.aggregatedEarlyEntryPointDeps()), - processingEnv()); - AliasOfs aliasOfs = - AliasOfs.create( - AliasOfPropagatedDataMetadata.from(metadata.aliasOfDeps()), componentDescriptors); - RootMetadata rootMetadata = RootMetadata.create(root, tree, deps, aliasOfs, processingEnv()); - - generateComponents(metadata, rootMetadata, componentNames); - - // Generate a creator for the early entry point if there is a default component available - // and there are early entry points. - if (isDefaultRoot && !metadata.aggregatedEarlyEntryPointDeps().isEmpty()) { - EarlySingletonComponentCreatorGenerator.generate(processingEnv()); - } - - if (root.isTestRoot()) { - // Generate test related classes for each test root that uses this component. - ImmutableList rootMetadatas = - roots.stream() - .map(test -> RootMetadata.create(test, tree, deps, aliasOfs, processingEnv())) - .collect(toImmutableList()); - generateTestComponentData(metadataElement, rootMetadatas, componentNames); - } else { - generateApplication(root.element()); - } - - setProcessingState(metadata, root); - } catch (Exception e) { - processed.add(metadata.name()); - throw e; - } - } - - private void setProcessingState(ComponentTreeDepsMetadata metadata, Root root) { - processed.add(metadata.name()); - } - - private void generateComponents( - ComponentTreeDepsMetadata metadata, RootMetadata rootMetadata, ComponentNames componentNames) - throws IOException { - RootGenerator.generate(metadata, rootMetadata, componentNames, processingEnv()); - } - - private void generateTestComponentData( - XTypeElement metadataElement, - ImmutableList rootMetadatas, - ComponentNames componentNames) - throws IOException { - for (RootMetadata rootMetadata : rootMetadatas) { - // TODO(bcorso): Consider moving this check earlier into processEach. - XTypeElement testElement = rootMetadata.testRootMetadata().testElement(); - ProcessorErrors.checkState( - testElement.isPublic(), - testElement, - "Hilt tests must be public, but found: %s", - XElements.toStableString(testElement)); - new TestComponentDataGenerator(processingEnv(), metadataElement, rootMetadata, componentNames) - .generate(); - } - } - - private void generateApplication(XTypeElement rootElement) throws IOException { - // The generated application references the generated component so they must be generated - // in the same build unit. Thus, we only generate the application here if we're using the - // Hilt Gradle plugin's aggregating task. If we're using the aggregating processor, we need - // to generate the application within AndroidEntryPointProcessor instead. - if (!useAggregatingRootProcessor(processingEnv())) { - AndroidEntryPointMetadata metadata = AndroidEntryPointMetadata.of(rootElement); - new ApplicationGenerator(processingEnv(), metadata).generate(); - } + protected BaseProcessingStep processingStep() { + return new ComponentTreeDepsProcessingStep(getXProcessingEnv()); } } diff --git a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java index 8b4bd3ff515..ccd64df5095 100644 --- a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java +++ b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java @@ -67,7 +67,7 @@ public void testDefineComponentOutput() { "package dagger.hilt.processor.internal.definecomponent.codegen;", "", "@DefineComponentClasses(component = \"test.FooComponent\")", - "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")", + "@Generated(\"" + DefineComponentProcessingStep.class.getName() + "\")", "public class _test_FooComponent {}"); JavaFileObject builderOutput = @@ -76,7 +76,7 @@ public void testDefineComponentOutput() { "package dagger.hilt.processor.internal.definecomponent.codegen;", "", "@DefineComponentClasses(builder = \"test.FooComponentBuilder\")", - "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")", + "@Generated(\"" + DefineComponentProcessingStep.class.getName() + "\")", "public class _test_FooComponentBuilder {}"); Compilation compilation = compiler().compile(component, builder);