dagger.hilt.processor.internal.root.ComponentTreeDepsProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hilt-android-compiler Show documentation
Show all versions of hilt-android-compiler Show documentation
A fast dependency injector for Android and Java.
/*
* 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.auto.common.MoreElements.asType;
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 javax.lang.model.element.Modifier.PUBLIC;
import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
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 java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
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<>();
private final DefineComponents defineComponents = DefineComponents.create();
@Override
public ImmutableSet getSupportedAnnotationTypes() {
return ImmutableSet.of(ClassNames.COMPONENT_TREE_DEPS.toString());
}
@Override
public void processEach(TypeElement annotation, Element element) {
componentTreeDepNames.add(ClassName.get(asType(element)));
}
@Override
public void postRoundProcess(RoundEnvironment roundEnv) throws Exception {
ImmutableSet componentTreeDepsToProcess =
componentTreeDepNames.stream()
.filter(className -> !processed.contains(className))
.map(className -> getElementUtils().getTypeElement(className.canonicalName()))
.map(element -> ComponentTreeDepsMetadata.from(element, getElementUtils()))
.collect(toImmutableSet());
for (ComponentTreeDepsMetadata metadata : componentTreeDepsToProcess) {
processComponentTreeDeps(metadata);
}
}
private void processComponentTreeDeps(ComponentTreeDepsMetadata metadata) throws IOException {
TypeElement metadataElement = getElementUtils().getTypeElement(metadata.name().canonicalName());
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, getProcessingEnv()))
.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(getProcessingEnv())
// Non-default roots should only ever be associated with one root element
: getOnlyElement(roots);
ImmutableSet componentDescriptors =
defineComponents.getComponentDescriptors(
DefineComponentClassesMetadata.from(
metadata.defineComponentDeps(), getElementUtils()));
ComponentTree tree = ComponentTree.from(componentDescriptors);
ComponentDependencies deps =
ComponentDependencies.from(
componentDescriptors,
AggregatedDepsMetadata.from(metadata.aggregatedDeps(), getElementUtils()),
AggregatedUninstallModulesMetadata.from(
metadata.aggregatedUninstallModulesDeps(), getElementUtils()),
AggregatedEarlyEntryPointMetadata.from(
metadata.aggregatedEarlyEntryPointDeps(), getElementUtils()),
getElementUtils());
AliasOfs aliasOfs =
AliasOfs.create(
AliasOfPropagatedDataMetadata.from(metadata.aliasOfDeps(), getElementUtils()),
componentDescriptors);
RootMetadata rootMetadata =
RootMetadata.create(root, tree, deps, aliasOfs, getProcessingEnv());
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(getProcessingEnv());
}
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, getProcessingEnv()))
.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, getProcessingEnv());
}
private void generateTestComponentData(
TypeElement metadataElement,
ImmutableList rootMetadatas,
ComponentNames componentNames)
throws IOException {
for (RootMetadata rootMetadata : rootMetadatas) {
// TODO(bcorso): Consider moving this check earlier into processEach.
TypeElement testElement = rootMetadata.testRootMetadata().testElement();
ProcessorErrors.checkState(
testElement.getModifiers().contains(PUBLIC),
testElement,
"Hilt tests must be public, but found: %s",
testElement);
new TestComponentDataGenerator(
getProcessingEnv(), metadataElement, rootMetadata, componentNames)
.generate();
}
}
private void generateApplication(TypeElement 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(getProcessingEnv())) {
AndroidEntryPointMetadata metadata =
AndroidEntryPointMetadata.of(getProcessingEnv(), rootElement);
new ApplicationGenerator(getProcessingEnv(), metadata).generate();
}
}
}