dagger.hilt.processor.internal.root.ComponentTreeDepsProcessingStep Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hilt-compiler Show documentation
Show all versions of hilt-compiler Show documentation
A fast dependency injector for Android and Java.
The newest version!
/*
* 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();
}
}
}