micronaut-core: IntelliJ incremental build not working when modifying a class that is directly injected.
Expected Behavior
Ideally, IntelliJ with enabled “Build and run using” -> “IntelliJ IDEA” should be able to leverage its incremental build when Micronaut managed beans are modified.
Incremental build takes usually few milliseconds comparing to a full module rebuild which may take 30 second or 2 minutes or even longer. In such case, when a Micronaut based application has many beans, development becomes extremely painful especially if people write their code + tests in a TDD cycle with small increments.
Note that the full rebuild is triggered when a particular class that is being injected is changed. Such as
- If an impl class is injected, then when that class is changed, a full rebuild is triggered.
- if an interface is injected, then when that interface is changed, a full rebuild is triggered.
whereas
- If an interface is injected, then modifying its impl doesn’t make IntelliJ to do a full rebuild.
Based on what I investigated, Micronaut 2 (2.2.1, 2.4.3, 2.5.13) and 3 (3.4.4) suffer from that behavior. However Micronaut 1 (I tried 1.3.7) was able to do incremental compilation.
In our project with 150 beans we suddenly hit the threshold. The full rebuild is taking more than 1 minute and because we like to inject beans without declaring interfaces for them (some folks are old enough to remember the J2EE days and are refusing to accept a workaround to “always” create an interface) we’re now completely desperate because of the slow turn around.
This is how incremental compilation in IntelliJ looks like:
Actual Behaviour
When modifying a class file of a bean that is injected somewhere else, IntelliJ full rebuild is triggered.
This is what IntelliJ logs when MyService
is injected into DemoController
directly and is changed:
2022-05-25 15:52:55,193 [ 32193] FINE - #o.j.j.b.j.d.Mappings - Begin of Differentiate:
2022-05-25 15:52:55,193 [ 32193] FINE - #o.j.j.b.j.d.Mappings - Easy mode: false
2022-05-25 15:52:55,198 [ 32198] FINE - #c.i.u.i.FileChannelWithSizeTracking - Inst:/Users/stepan.vavra/Library/Caches/JetBrains/IntelliJIdea2022.1/compile-server/demo_7ab3cf90/mappings/sourceToClass.tab.values@33bd46cc,Thread[JPS thread pool,5,main],java.net.URLClassLoader@2626b418
2022-05-25 15:52:55,198 [ 32198] FINE - #c.i.u.i.FileChannelWithSizeTracking - read:/Users/stepan.vavra/Library/Caches/JetBrains/IntelliJIdea2022.1/compile-server/demo_7ab3cf90/mappings/sourceToClass.tab.values@33bd46cc,Thread[JPS thread pool,5,main],7412,168355
2022-05-25 15:52:55,208 [ 32208] FINE - #o.j.j.b.j.d.Mappings - Processing changed classes:
2022-05-25 15:52:55,209 [ 32209] FINE - #o.j.j.b.j.d.Mappings - Changed: com/example/MyService
2022-05-25 15:52:55,213 [ 32213] FINE - #o.j.j.b.j.d.Mappings - End of changed classes processing
2022-05-25 15:52:55,213 [ 32213] FINE - #o.j.j.b.j.d.Mappings - Checking dependent classes:
2022-05-25 15:52:55,216 [ 32216] FINE - #o.j.j.b.j.d.Mappings - Dependent class: com/example/$MyService$Definition$Reference
2022-05-25 15:52:55,228 [ 32228] FINE - #o.j.j.b.j.d.Mappings - Dependent class: com/example/$DemoController$Definition
2022-05-25 15:52:55,230 [ 32230] FINE - #o.j.j.b.j.d.Mappings - Turning non-incremental for the BuildTarget because dependent class is annotation-processor generated
2022-05-25 15:52:55,231 [ 32231] FINE - #o.j.j.b.j.d.Mappings - Dependent class: com/example/DemoController
2022-05-25 15:52:55,231 [ 32231] FINE - #o.j.j.b.j.d.Mappings - Dependent class: com/example/$MyService$Definition
2022-05-25 15:52:55,232 [ 32232] FINE - #o.j.j.i.Builder - Differentiate Results:
2022-05-25 15:52:55,232 [ 32232] FINE - #o.j.j.i.Builder - Compiled Files:
2022-05-25 15:52:55,232 [ 32232] FINE - #o.j.j.i.Builder - /Users/stepan.vavra/Repos/other/micronaut-test-slow-compilation/src/main/java/com/example/MyService.java
2022-05-25 15:52:55,233 [ 32233] FINE - #o.j.j.i.Builder - Affected Files:
2022-05-25 15:52:55,233 [ 32233] FINE - #o.j.j.i.Builder - End Of Differentiate Results.
2022-05-25 15:52:55,233 [ 32233] INFO - #o.j.j.i.Builder - Non-incremental mode: Marking demo.main and direct dependants for recompilation
and a full rebuild is triggered.
This is how a full rebuild looks like in IntelliJ:
Steps To Reproduce
Consider a simple demo app
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import jakarta.inject.Inject;
@Controller
public class DemoController {
private final MyService myService;
@Inject
public DemoController(MyService myService) {
this.myService = myService;
}
@Get("/message")
String message() {
return myService.hello("hello");
}
}
with a service
import jakarta.inject.Singleton;
@Singleton
class MyService {
public String hello(String input) {
// comment and uncomment next 2 lines to get a full rebuild
return "foo".equals(input) ? "hello3" : "HELLO";
// return "foo".equals(input.toLowerCase()) ? "hello3" : "HELLO";
}
}
When the MyService
is changed in a way that its bytecode significantly changes (i.e., it’s not enough to change the string constants), that is to do for instance
return "foo".equals(input.toLowerCase()) ? "hello" : "HELLO";
Then when “Build” -> “Build module” is executed, IntelliJ marks the module for a full recompilation
Checking dependencies… [demo.main]
Marking demo.main and direct dependants for recompilation
Environment Information
As mentioned above, this problem happens with Micronaut 2.5.13 and 3.4.4 I tried IntelliJ 2022.1, 2021.3.3, 2021.3.2. JDK 17 and 11 on Mac.
Example Application
https://github.com/stepanv/micronaut-full-rebuild-sample-app
Version
3.4.4
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 17 (17 by maintainers)
Thanks @graemerocher , that works perfectly!
Is the
flaw of Micronaut or is it a Gradle thing? I have the reproducer ready so I’m ready to report it right away.