Skip to content

Commit af88de2

Browse files
authored
Dependency resolving process in graph build errors (#217)
1 parent cd6470e commit af88de2

File tree

10 files changed

+102
-20
lines changed

10 files changed

+102
-20
lines changed

annotation-processor-common/src/main/java/ru/tinkoff/kora/annotation/processor/common/BuildEnvironment.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import org.slf4j.LoggerFactory;
1010

1111
import javax.annotation.processing.ProcessingEnvironment;
12-
import javax.tools.Diagnostic;
1312
import javax.tools.StandardLocation;
1413
import java.io.IOException;
1514
import java.nio.file.Path;
@@ -37,11 +36,9 @@ public static synchronized void init(ProcessingEnvironment processingEnv) {
3736
} else if (dir.getFileName().toString().startsWith("generated-")) {
3837
buildDir = dir.getParent();
3938
} else {
40-
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Build dir was no detected, there will be no build log for kora");
4139
return;
4240
}
4341
} catch (IOException e) {
44-
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Build dir was no detected, there will be no build log for kora");
4542
return;
4643
}
4744
initLog(processingEnv);

kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/GraphBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static ProcessingState processProcessing(ProcessingContext ctx, RoundEnvi
3838
return new ProcessingState.Failed(new ProcessingErrorException(
3939
"@KoraApp has no root components, expected at least one component annotated with @Root",
4040
processing.root()
41-
));
41+
), new ArrayDeque<>());
4242
}
4343
var stack = processing.resolutionStack();
4444
frame:

kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessor.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
126126
}
127127
} catch (ProcessingErrorException e) {
128128
log.info("Processing exception", e);
129-
results.put(annotatedClass.getKey(), new ProcessingState.Failed(e));
129+
results.put(annotatedClass.getKey(), new ProcessingState.Failed(e, processingResult.stack()));
130130
} catch (Exception e) {
131131
if (e instanceof FilerException || e.getCause() instanceof FilerException) {
132132
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, e.getMessage());
@@ -146,6 +146,22 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
146146
}
147147
if (processingResult instanceof ProcessingState.Failed failed) {
148148
failed.detailedException().printError(this.processingEnv);
149+
if (!failed.stack().isEmpty()) {
150+
var i = processingResult.stack().descendingIterator();
151+
var frames = new ArrayList<ProcessingState.ResolutionFrame.Component>();
152+
while (i.hasNext()) {
153+
var frame = i.next();
154+
if (frame instanceof ProcessingState.ResolutionFrame.Component c) {
155+
frames.add(0, c);
156+
} else {
157+
break;
158+
}
159+
}
160+
var chain = frames.stream()
161+
.map(c -> c.declaration().declarationString() + " " + c.dependenciesToFind().get(c.currentDependency()))
162+
.collect(Collectors.joining("\n | \n ^ \n"));
163+
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Dependency resolve process: \n" + chain);
164+
}
149165
}
150166
if (processingResult instanceof ProcessingState.Ok ok) {
151167
try {
@@ -232,7 +248,7 @@ private ProcessingState.Processing processNone(ProcessingState.None none) {
232248

233249
private ProcessingState parseNone(Element classElement) {
234250
if (classElement.getKind() != ElementKind.INTERFACE) {
235-
return new ProcessingState.Failed(new ProcessingErrorException("@KoraApp is only applicable to interfaces", classElement));
251+
return new ProcessingState.Failed(new ProcessingErrorException("@KoraApp is only applicable to interfaces", classElement), new ArrayDeque<>());
236252
}
237253
try {
238254
var type = (TypeElement) classElement;
@@ -269,7 +285,7 @@ record Components(List<ComponentDeclaration> templates, List<ComponentDeclaratio
269285
.toList();
270286
return new ProcessingState.None(type, allModules, sourceDescriptors, components.templates, rootSet);
271287
} catch (ProcessingErrorException e) {
272-
return new ProcessingState.Failed(e);
288+
return new ProcessingState.Failed(e, new ArrayDeque<>());
273289
}
274290
}
275291

kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/ProcessingState.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99
import javax.annotation.Nullable;
1010
import javax.lang.model.element.TypeElement;
1111
import javax.lang.model.type.TypeMirror;
12-
import java.util.ArrayList;
13-
import java.util.Deque;
14-
import java.util.List;
15-
import java.util.Set;
12+
import java.util.*;
1613

1714
public sealed interface ProcessingState {
1815
sealed interface ResolutionFrame {
@@ -29,7 +26,14 @@ public Component withCurrentDependency(int currentDependency) {
2926
}
3027
}
3128

32-
record None(TypeElement root, List<TypeElement> allModules, List<ComponentDeclaration> sourceDeclarations, List<ComponentDeclaration> templates, List<ComponentDeclaration> rootSet) implements ProcessingState {}
29+
default Deque<ResolutionFrame> stack() {
30+
return this instanceof Processing processing
31+
? processing.resolutionStack
32+
: new ArrayDeque<>();
33+
}
34+
35+
record None(TypeElement root, List<TypeElement> allModules, List<ComponentDeclaration> sourceDeclarations, List<ComponentDeclaration> templates,
36+
List<ComponentDeclaration> rootSet) implements ProcessingState {}
3337

3438
record Processing(TypeElement root, List<TypeElement> allModules, List<ComponentDeclaration> sourceDeclarations, List<ComponentDeclaration> templates, List<ComponentDeclaration> rootSet,
3539
List<ResolvedComponent> resolvedComponents, Deque<ResolutionFrame> resolutionStack) implements ProcessingState {
@@ -49,5 +53,5 @@ record Ok(TypeElement root, List<TypeElement> allModules, List<ResolvedComponent
4953

5054
record NewRoundRequired(Object source, TypeMirror type, Set<String> tag, Processing processing) implements ProcessingState {}
5155

52-
record Failed(ProcessingErrorException detailedException) implements ProcessingState {}
56+
record Failed(ProcessingErrorException detailedException, Deque<ResolutionFrame> stack) implements ProcessingState {}
5357
}

kora-app-annotation-processor/src/main/java/ru/tinkoff/kora/kora/app/annotation/processor/declaration/ComponentDeclaration.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ default boolean isDefault() {
2727

2828
boolean isInterceptor();
2929

30+
String declarationString();
31+
3032
record FromModuleComponent(TypeMirror type, ModuleDeclaration module, Set<String> tags, ExecutableElement method, List<TypeMirror> methodParameterTypes,
3133
List<TypeMirror> typeVariables, boolean isInterceptor) implements ComponentDeclaration {
3234
@Override
@@ -38,6 +40,11 @@ public Element source() {
3840
public boolean isDefault() {
3941
return AnnotationUtils.findAnnotation(this.method, CommonClassNames.defaultComponent) != null;
4042
}
43+
44+
@Override
45+
public String declarationString() {
46+
return module.element().getQualifiedName() + "." + method.getSimpleName();
47+
}
4148
}
4249

4350
record AnnotatedComponent(TypeMirror type, TypeElement typeElement, Set<String> tags, ExecutableElement constructor, List<TypeMirror> methodParameterTypes,
@@ -46,6 +53,11 @@ record AnnotatedComponent(TypeMirror type, TypeElement typeElement, Set<String>
4653
public Element source() {
4754
return this.constructor;
4855
}
56+
57+
@Override
58+
public String declarationString() {
59+
return typeElement.getQualifiedName().toString();
60+
}
4961
}
5062

5163
record DiscoveredAsDependencyComponent(TypeMirror type, TypeElement typeElement, ExecutableElement constructor, Set<String> tags) implements ComponentDeclaration {
@@ -67,6 +79,11 @@ public boolean isTemplate() {
6779
public boolean isInterceptor() {
6880
return false;
6981
}
82+
83+
@Override
84+
public String declarationString() {
85+
return typeElement.getQualifiedName().toString();
86+
}
7087
}
7188

7289
record FromExtensionComponent(TypeMirror type, ExecutableElement sourceMethod, List<TypeMirror> methodParameterTypes) implements ComponentDeclaration {
@@ -84,6 +101,11 @@ public Set<String> tags() {
84101
public boolean isInterceptor() {
85102
return false;
86103
}
104+
105+
@Override
106+
public String declarationString() {
107+
return sourceMethod.getEnclosingElement().toString() + "." + sourceMethod.getSimpleName();
108+
}
87109
}
88110

89111
record PromisedProxyComponent(TypeElement typeElement, TypeMirror type, com.squareup.javapoet.ClassName className) implements ComponentDeclaration {
@@ -110,6 +132,11 @@ public Set<String> tags() {
110132
public boolean isInterceptor() {
111133
return false;
112134
}
135+
136+
@Override
137+
public String declarationString() {
138+
return "<Proxy>";
139+
}
113140
}
114141

115142
record OptionalComponent(TypeMirror type, Set<String> tags) implements ComponentDeclaration {
@@ -122,6 +149,11 @@ public Element source() {
122149
public boolean isInterceptor() {
123150
return false;
124151
}
152+
153+
@Override
154+
public String declarationString() {
155+
return "<EmptyOptional>";
156+
}
125157
}
126158

127159
static ComponentDeclaration fromModule(ProcessingContext ctx, ModuleDeclaration module, ExecutableElement method) {

kora-app-annotation-processor/src/test/java/ru/tinkoff/kora/kora/app/annotation/processor/KoraAppProcessorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ void appWithAllOf() throws Throwable {
153153
void unresolvedDependency() {
154154
assertThatThrownBy(() -> testClass(AppWithUnresolvedDependency.class))
155155
.isInstanceOfSatisfying(CompilationErrorException.class, e -> SoftAssertions.assertSoftly(s -> {
156-
s.assertThat(e.getMessage()).isEqualTo("""
156+
s.assertThat(e.getMessage()).startsWith("""
157157
Required dependency type was not found and can't be auto created: ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithUnresolvedDependency.Class3.
158158
Please check class for @Component annotation or that required module with component is plugged in.
159159
Requested at: ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithUnresolvedDependency.class2(ru.tinkoff.kora.kora.app.annotation.processor.app.AppWithUnresolvedDependency.Class3)""");

kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/GraphBuilder.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ object GraphBuilder {
3030
ProcessingErrorException(
3131
"@KoraApp has no root components, expected at least one component annotated with @Root",
3232
p.root
33-
)
33+
),
34+
p.resolutionStack
3435
)
3536
}
3637
var processing = p;

kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/KoraAppProcessor.kt

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,22 @@ class KoraAppProcessor(
6060
when (processingResult) {
6161
is ProcessingState.Failed -> {
6262
processingResult.exception.printError(kspLogger)
63-
throw processingResult.exception
63+
if (processingResult.resolutionStack.isNotEmpty()) {
64+
val i = processingResult.resolutionStack.descendingIterator()
65+
val frames = ArrayList<ProcessingState.ResolutionFrame.Component>()
66+
while (i.hasNext()) {
67+
val frame = i.next()
68+
if (frame is ProcessingState.ResolutionFrame.Component) {
69+
frames.add(0, frame)
70+
} else {
71+
break
72+
}
73+
}
74+
val chain = frames.joinToString("\n | \n ^ \n") {
75+
it.declaration.declarationString() + " " + it.dependenciesToFind[it.currentDependency]
76+
}
77+
kspLogger.warn("Dependency resolve process: $chain")
78+
}
6479
}
6580

6681
is ProcessingState.NewRoundRequired -> {
@@ -152,7 +167,7 @@ class KoraAppProcessor(
152167
e.resolving
153168
)
154169
} catch (e: ProcessingErrorException) {
155-
results[actualKey] = declaration to ProcessingState.Failed(e)
170+
results[actualKey] = declaration to ProcessingState.Failed(e, processingResult.stack())
156171
}
157172
}
158173
processedDeclarations.putAll(results)
@@ -176,7 +191,7 @@ class KoraAppProcessor(
176191

177192
private fun parseNone(declaration: KSClassDeclaration): ProcessingState {
178193
if (declaration.classKind != ClassKind.INTERFACE) {
179-
return ProcessingState.Failed(ProcessingErrorException("@KoraApp is only applicable to interfaces", declaration))
194+
return ProcessingState.Failed(ProcessingErrorException("@KoraApp is only applicable to interfaces", declaration), ArrayDeque())
180195
}
181196
try {
182197
val rootErasure = declaration.asStarProjectedType()
@@ -237,7 +252,7 @@ class KoraAppProcessor(
237252
}
238253
return ProcessingState.None(declaration, allModules, components, templateComponents, rootSet)
239254
} catch (e: ProcessingErrorException) {
240-
return ProcessingState.Failed(e)
255+
return ProcessingState.Failed(e, ArrayDeque())
241256
}
242257
}
243258

kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/ProcessingState.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ sealed interface ProcessingState {
2121
) : ResolutionFrame
2222
}
2323

24+
fun stack() = if (this is Processing) {
25+
this.resolutionStack
26+
} else {
27+
ArrayDeque()
28+
}
29+
2430
data class None(
2531
val root: KSClassDeclaration,
2632
val allModules: List<KSClassDeclaration>,
@@ -45,5 +51,5 @@ sealed interface ProcessingState {
4551

4652
data class Ok(val root: KSClassDeclaration, val allModules: List<KSClassDeclaration>, val components: List<ResolvedComponent>) : ProcessingState
4753
data class NewRoundRequired(val source: Any, val type: KSType, val tag: Set<String>, val processing: Processing) : ProcessingState
48-
data class Failed(val exception: ProcessingErrorException) : ProcessingState
54+
data class Failed(val exception: ProcessingErrorException, val resolutionStack: Deque<ResolutionFrame>) : ProcessingState
4955
}

kora-app-symbol-processor/src/main/kotlin/ru/tinkoff/kora/kora/app/ksp/declaration/ComponentDeclaration.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ sealed interface ComponentDeclaration {
1515
val source: KSDeclaration
1616
val tags: Set<String>
1717

18+
fun declarationString(): String
19+
1820
fun isTemplate(): Boolean {
1921
for (argument in type.arguments) {
2022
if (argument.hasGenericVariable()) {
@@ -37,6 +39,8 @@ sealed interface ComponentDeclaration {
3739
val typeVariables: List<KSTypeArgument>
3840
) : ComponentDeclaration {
3941
override val source get() = this.method
42+
override fun declarationString() = module.element.qualifiedName?.asString() + "." + method.simpleName.asString()
43+
4044
override fun isDefault(): Boolean {
4145
return method.findAnnotation(CommonClassNames.defaultComponent) != null
4246
}
@@ -51,6 +55,7 @@ sealed interface ComponentDeclaration {
5155
val typeVariables: List<KSTypeArgument>
5256
) : ComponentDeclaration {
5357
override val source get() = this.constructor
58+
override fun declarationString() = classDeclaration.qualifiedName?.asString().toString()
5459
}
5560

5661
data class DiscoveredAsDependencyComponent(
@@ -60,6 +65,7 @@ sealed interface ComponentDeclaration {
6065
override val tags: Set<String>
6166
) : ComponentDeclaration {
6267
override val source get() = this.constructor
68+
override fun declarationString() = classDeclaration.qualifiedName?.asString().toString()
6369
}
6470

6571
data class FromExtensionComponent(
@@ -70,6 +76,9 @@ sealed interface ComponentDeclaration {
7076
) : ComponentDeclaration {
7177
override val source get() = this.sourceMethod
7278
override val tags get() = setOf<String>()
79+
override fun declarationString(): String {
80+
return sourceMethod.parentDeclaration?.qualifiedName?.asString().toString() + sourceMethod.simpleName.asString()
81+
}
7382

7483
}
7584

@@ -81,6 +90,7 @@ sealed interface ComponentDeclaration {
8190
) : ComponentDeclaration {
8291
override val source get() = this.classDeclaration
8392
override val tags get() = setOf(CommonClassNames.promisedProxy.canonicalName)
93+
override fun declarationString() = "<Proxy>"
8494
}
8595

8696

@@ -89,6 +99,7 @@ sealed interface ComponentDeclaration {
8999
override val tags: Set<String>
90100
) : ComponentDeclaration {
91101
override val source get() = type.declaration
102+
override fun declarationString() = "Optional.empty"
92103
}
93104

94105

0 commit comments

Comments
 (0)