From af88de285f598ecf48c133c6fdb91d6aeaae5d52 Mon Sep 17 00:00:00 2001 From: Anton Duyun Date: Mon, 24 Jul 2023 19:54:46 +0300 Subject: [PATCH] Dependency resolving process in graph build errors (#217) --- .../processor/common/BuildEnvironment.java | 3 -- .../annotation/processor/GraphBuilder.java | 2 +- .../processor/KoraAppProcessor.java | 22 +++++++++++-- .../annotation/processor/ProcessingState.java | 16 ++++++---- .../declaration/ComponentDeclaration.java | 32 +++++++++++++++++++ .../processor/KoraAppProcessorTest.java | 2 +- .../tinkoff/kora/kora/app/ksp/GraphBuilder.kt | 3 +- .../kora/kora/app/ksp/KoraAppProcessor.kt | 23 ++++++++++--- .../kora/kora/app/ksp/ProcessingState.kt | 8 ++++- .../ksp/declaration/ComponentDeclaration.kt | 11 +++++++ 10 files changed, 102 insertions(+), 20 deletions(-) diff --git a/annotation-processor-common/src/main/java/ru/tinkoff/kora/annotation/processor/common/BuildEnvironment.java b/annotation-processor-common/src/main/java/ru/tinkoff/kora/annotation/processor/common/BuildEnvironment.java index cc5abd975..2d083cd89 100644 --- a/annotation-processor-common/src/main/java/ru/tinkoff/kora/annotation/processor/common/BuildEnvironment.java +++ b/annotation-processor-common/src/main/java/ru/tinkoff/kora/annotation/processor/common/BuildEnvironment.java @@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory; import javax.annotation.processing.ProcessingEnvironment; -import javax.tools.Diagnostic; import javax.tools.StandardLocation; import java.io.IOException; import java.nio.file.Path; @@ -37,11 +36,9 @@ public static synchronized void init(ProcessingEnvironment processingEnv) { } else if (dir.getFileName().toString().startsWith("generated-")) { buildDir = dir.getParent(); } else { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Build dir was no detected, there will be no build log for kora"); return; } } catch (IOException e) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Build dir was no detected, there will be no build log for kora"); return; } initLog(processingEnv); diff --git a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java index 271c1abc3..3bb3f80c2 100644 --- a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java +++ b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java @@ -38,7 +38,7 @@ public static ProcessingState processProcessing(ProcessingContext ctx, RoundEnvi return new ProcessingState.Failed(new ProcessingErrorException( "@KoraApp has no root components, expected at least one component annotated with @Root", processing.root() - )); + ), new ArrayDeque<>()); } var stack = processing.resolutionStack(); frame: diff --git a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessor.java b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessor.java index 21a76d700..e732da52f 100644 --- a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessor.java +++ b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessor.java @@ -126,7 +126,7 @@ public boolean process(Set annotations, RoundEnvironment } } catch (ProcessingErrorException e) { log.info("Processing exception", e); - results.put(annotatedClass.getKey(), new ProcessingState.Failed(e)); + results.put(annotatedClass.getKey(), new ProcessingState.Failed(e, processingResult.stack())); } catch (Exception e) { if (e instanceof FilerException || e.getCause() instanceof FilerException) { this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, e.getMessage()); @@ -146,6 +146,22 @@ public boolean process(Set annotations, RoundEnvironment } if (processingResult instanceof ProcessingState.Failed failed) { failed.detailedException().printError(this.processingEnv); + if (!failed.stack().isEmpty()) { + var i = processingResult.stack().descendingIterator(); + var frames = new ArrayList(); + while (i.hasNext()) { + var frame = i.next(); + if (frame instanceof ProcessingState.ResolutionFrame.Component c) { + frames.add(0, c); + } else { + break; + } + } + var chain = frames.stream() + .map(c -> c.declaration().declarationString() + " " + c.dependenciesToFind().get(c.currentDependency())) + .collect(Collectors.joining("\n | \n ^ \n")); + this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Dependency resolve process: \n" + chain); + } } if (processingResult instanceof ProcessingState.Ok ok) { try { @@ -232,7 +248,7 @@ private ProcessingState.Processing processNone(ProcessingState.None none) { private ProcessingState parseNone(Element classElement) { if (classElement.getKind() != ElementKind.INTERFACE) { - return new ProcessingState.Failed(new ProcessingErrorException("@KoraApp is only applicable to interfaces", classElement)); + return new ProcessingState.Failed(new ProcessingErrorException("@KoraApp is only applicable to interfaces", classElement), new ArrayDeque<>()); } try { var type = (TypeElement) classElement; @@ -269,7 +285,7 @@ record Components(List templates, List()); } } diff --git a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/ProcessingState.java b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/ProcessingState.java index 75593120c..969210893 100644 --- a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/ProcessingState.java +++ b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/ProcessingState.java @@ -9,10 +9,7 @@ import javax.annotation.Nullable; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import java.util.ArrayList; -import java.util.Deque; -import java.util.List; -import java.util.Set; +import java.util.*; public sealed interface ProcessingState { sealed interface ResolutionFrame { @@ -29,7 +26,14 @@ public Component withCurrentDependency(int currentDependency) { } } - record None(TypeElement root, List allModules, List sourceDeclarations, List templates, List rootSet) implements ProcessingState {} + default Deque stack() { + return this instanceof Processing processing + ? processing.resolutionStack + : new ArrayDeque<>(); + } + + record None(TypeElement root, List allModules, List sourceDeclarations, List templates, + List rootSet) implements ProcessingState {} record Processing(TypeElement root, List allModules, List sourceDeclarations, List templates, List rootSet, List resolvedComponents, Deque resolutionStack) implements ProcessingState { @@ -49,5 +53,5 @@ record Ok(TypeElement root, List allModules, List tag, Processing processing) implements ProcessingState {} - record Failed(ProcessingErrorException detailedException) implements ProcessingState {} + record Failed(ProcessingErrorException detailedException, Deque stack) implements ProcessingState {} } diff --git a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/declaration/ComponentDeclaration.java b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/declaration/ComponentDeclaration.java index 41ac872f8..a570b70c3 100644 --- a/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/declaration/ComponentDeclaration.java +++ b/kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/declaration/ComponentDeclaration.java @@ -27,6 +27,8 @@ default boolean isDefault() { boolean isInterceptor(); + String declarationString(); + record FromModuleComponent(TypeMirror type, ModuleDeclaration module, Set tags, ExecutableElement method, List methodParameterTypes, List typeVariables, boolean isInterceptor) implements ComponentDeclaration { @Override @@ -38,6 +40,11 @@ public Element source() { public boolean isDefault() { return AnnotationUtils.findAnnotation(this.method, CommonClassNames.defaultComponent) != null; } + + @Override + public String declarationString() { + return module.element().getQualifiedName() + "." + method.getSimpleName(); + } } record AnnotatedComponent(TypeMirror type, TypeElement typeElement, Set tags, ExecutableElement constructor, List methodParameterTypes, @@ -46,6 +53,11 @@ record AnnotatedComponent(TypeMirror type, TypeElement typeElement, Set public Element source() { return this.constructor; } + + @Override + public String declarationString() { + return typeElement.getQualifiedName().toString(); + } } record DiscoveredAsDependencyComponent(TypeMirror type, TypeElement typeElement, ExecutableElement constructor, Set tags) implements ComponentDeclaration { @@ -67,6 +79,11 @@ public boolean isTemplate() { public boolean isInterceptor() { return false; } + + @Override + public String declarationString() { + return typeElement.getQualifiedName().toString(); + } } record FromExtensionComponent(TypeMirror type, ExecutableElement sourceMethod, List methodParameterTypes) implements ComponentDeclaration { @@ -84,6 +101,11 @@ public Set tags() { public boolean isInterceptor() { return false; } + + @Override + public String declarationString() { + return sourceMethod.getEnclosingElement().toString() + "." + sourceMethod.getSimpleName(); + } } record PromisedProxyComponent(TypeElement typeElement, TypeMirror type, com.squareup.javapoet.ClassName className) implements ComponentDeclaration { @@ -110,6 +132,11 @@ public Set tags() { public boolean isInterceptor() { return false; } + + @Override + public String declarationString() { + return ""; + } } record OptionalComponent(TypeMirror type, Set tags) implements ComponentDeclaration { @@ -122,6 +149,11 @@ public Element source() { public boolean isInterceptor() { return false; } + + @Override + public String declarationString() { + return ""; + } } static ComponentDeclaration fromModule(ProcessingContext ctx, ModuleDeclaration module, ExecutableElement method) { diff --git a/kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java b/kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java index 56e6edcef..c9871d539 100644 --- a/kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java +++ b/kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java @@ -153,7 +153,7 @@ void appWithAllOf() throws Throwable { void unresolvedDependency() { assertThatThrownBy(() -> testClass(AppWithUnresolvedDependency.class)) .isInstanceOfSatisfying(CompilationErrorException.class, e -> SoftAssertions.assertSoftly(s -> { - s.assertThat(e.getMessage()).isEqualTo(""" + s.assertThat(e.getMessage()).startsWith(""" Required dependency type was not found and can't be auto created: ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithUnresolvedDependency.Class3. Please check class for @Component annotation or that required module with component is plugged in. Requested at: ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithUnresolvedDependency.class2(ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithUnresolvedDependency.Class3)"""); diff --git a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt index a54b08200..4073b60f7 100644 --- a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt +++ b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt @@ -30,7 +30,8 @@ object GraphBuilder { ProcessingErrorException( "@KoraApp has no root components, expected at least one component annotated with @Root", p.root - ) + ), + p.resolutionStack ) } var processing = p; diff --git a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/KoraAppProcessor.kt b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/KoraAppProcessor.kt index 2a3001663..32e528654 100644 --- a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/KoraAppProcessor.kt +++ b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/KoraAppProcessor.kt @@ -60,7 +60,22 @@ class KoraAppProcessor( when (processingResult) { is ProcessingState.Failed -> { processingResult.exception.printError(kspLogger) - throw processingResult.exception + if (processingResult.resolutionStack.isNotEmpty()) { + val i = processingResult.resolutionStack.descendingIterator() + val frames = ArrayList() + while (i.hasNext()) { + val frame = i.next() + if (frame is ProcessingState.ResolutionFrame.Component) { + frames.add(0, frame) + } else { + break + } + } + val chain = frames.joinToString("\n | \n ^ \n") { + it.declaration.declarationString() + " " + it.dependenciesToFind[it.currentDependency] + } + kspLogger.warn("Dependency resolve process: $chain") + } } is ProcessingState.NewRoundRequired -> { @@ -152,7 +167,7 @@ class KoraAppProcessor( e.resolving ) } catch (e: ProcessingErrorException) { - results[actualKey] = declaration to ProcessingState.Failed(e) + results[actualKey] = declaration to ProcessingState.Failed(e, processingResult.stack()) } } processedDeclarations.putAll(results) @@ -176,7 +191,7 @@ class KoraAppProcessor( private fun parseNone(declaration: KSClassDeclaration): ProcessingState { if (declaration.classKind != ClassKind.INTERFACE) { - return ProcessingState.Failed(ProcessingErrorException("@KoraApp is only applicable to interfaces", declaration)) + return ProcessingState.Failed(ProcessingErrorException("@KoraApp is only applicable to interfaces", declaration), ArrayDeque()) } try { val rootErasure = declaration.asStarProjectedType() @@ -237,7 +252,7 @@ class KoraAppProcessor( } return ProcessingState.None(declaration, allModules, components, templateComponents, rootSet) } catch (e: ProcessingErrorException) { - return ProcessingState.Failed(e) + return ProcessingState.Failed(e, ArrayDeque()) } } diff --git a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/ProcessingState.kt b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/ProcessingState.kt index 7689b8e0e..797f6b137 100644 --- a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/ProcessingState.kt +++ b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/ProcessingState.kt @@ -21,6 +21,12 @@ sealed interface ProcessingState { ) : ResolutionFrame } + fun stack() = if (this is Processing) { + this.resolutionStack + } else { + ArrayDeque() + } + data class None( val root: KSClassDeclaration, val allModules: List, @@ -45,5 +51,5 @@ sealed interface ProcessingState { data class Ok(val root: KSClassDeclaration, val allModules: List, val components: List) : ProcessingState data class NewRoundRequired(val source: Any, val type: KSType, val tag: Set, val processing: Processing) : ProcessingState - data class Failed(val exception: ProcessingErrorException) : ProcessingState + data class Failed(val exception: ProcessingErrorException, val resolutionStack: Deque) : ProcessingState } diff --git a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/declaration/ComponentDeclaration.kt b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/declaration/ComponentDeclaration.kt index 4cae18387..93cb6e97e 100644 --- a/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/declaration/ComponentDeclaration.kt +++ b/kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/declaration/ComponentDeclaration.kt @@ -15,6 +15,8 @@ sealed interface ComponentDeclaration { val source: KSDeclaration val tags: Set + fun declarationString(): String + fun isTemplate(): Boolean { for (argument in type.arguments) { if (argument.hasGenericVariable()) { @@ -37,6 +39,8 @@ sealed interface ComponentDeclaration { val typeVariables: List ) : ComponentDeclaration { override val source get() = this.method + override fun declarationString() = module.element.qualifiedName?.asString() + "." + method.simpleName.asString() + override fun isDefault(): Boolean { return method.findAnnotation(CommonClassNames.defaultComponent) != null } @@ -51,6 +55,7 @@ sealed interface ComponentDeclaration { val typeVariables: List ) : ComponentDeclaration { override val source get() = this.constructor + override fun declarationString() = classDeclaration.qualifiedName?.asString().toString() } data class DiscoveredAsDependencyComponent( @@ -60,6 +65,7 @@ sealed interface ComponentDeclaration { override val tags: Set ) : ComponentDeclaration { override val source get() = this.constructor + override fun declarationString() = classDeclaration.qualifiedName?.asString().toString() } data class FromExtensionComponent( @@ -70,6 +76,9 @@ sealed interface ComponentDeclaration { ) : ComponentDeclaration { override val source get() = this.sourceMethod override val tags get() = setOf() + override fun declarationString(): String { + return sourceMethod.parentDeclaration?.qualifiedName?.asString().toString() + sourceMethod.simpleName.asString() + } } @@ -81,6 +90,7 @@ sealed interface ComponentDeclaration { ) : ComponentDeclaration { override val source get() = this.classDeclaration override val tags get() = setOf(CommonClassNames.promisedProxy.canonicalName) + override fun declarationString() = "" } @@ -89,6 +99,7 @@ sealed interface ComponentDeclaration { override val tags: Set ) : ComponentDeclaration { override val source get() = type.declaration + override fun declarationString() = "Optional.empty" }