Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev mode is not working in a gradle multimodule setup when kotlin code is used #35577

Open
jmini opened this issue Aug 26, 2023 · 33 comments
Open
Labels
area/devmode area/gradle Gradle area/jbang Issues related to when using jbang.dev with Quarkus area/kotlin kind/bug Something isn't working

Comments

@jmini
Copy link
Contributor

jmini commented Aug 26, 2023

Describe the bug

I am in a multi-module gradle project.

  • :core-lib shared lib
    • contains java and kotlin code (classes annotated with @ApplicationScoped)
    • jandex plugin is configured
    • kotlin for quarkus is configured (incl. the allopen plugin)
  • :app1 quarkus app
    • contains a Rest endpoint using @Inject to get the services from thecore-lib

In a real setup there are more modules (multiple shared modules, and multiple quarkus app that achieve different tasks of a more complex system)

Building the app and running it with java -jar app1/build/quarkus-app/quarkus-run.jar runs perfectly fine.
Running with dev mode ./gradlew app1:quarkusDev is failing:

jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.core.kt.ConfigService and qualifiers [@Default]
        - java member: org.acme.app1.App1Controller#config
        - declared on CLASS bean [types=[org.acme.app1.App1Controller, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.app1.App1Controller]
        at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:477)
        at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:624)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:299)
... 

(complete trace at the bottom of this message)

The error is similar to when the jandex plugin in not running in core-lib (from the docs: Working with multi-module gradle projects).
But this is not the case here. Jandex is configured and is working.
Also a work-around for kordamp/jandex-gradle-plugin#24 is in place.

But what is wired is that:

./gradlew (clean) build
java -jar app1/build/quarkus-app/quarkus-run.jar

works perfectly.

Also when we build the docker image of the quarkus app, everything is working correctly.

And the issue is only present when the core-lib module contains kotlin code. With only java code everything works as expected.

This is only a dev-mode issue.

How to Reproduce?

See repository:
https://github.com/jmini/quarkus_issue35577

Output of uname -a or ver

22.3.0 Darwin Kernel Version 22.3.0: Mon Jan 30 20:42:11 PST 2023; root:xnu-8792.81.3~2/RELEASE_X86_64 x86_64

macOS, but it doesn't matter.

We observe the same on Linux.

Output of java -version

openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment Temurin-17.0.5+8 (build 17.0.5+8)
OpenJDK 64-Bit Server VM Temurin-17.0.5+8 (build 17.0.5+8, mixed mode, sharing)

GraalVM version (if different from Java)

not relevant

Quarkus version or git rev

3.3.0 (reproducible as well with lower versions)

Build tool (ie. output of mvnw --version or gradlew --version)

------------------------------------------------------------
Gradle 8.1.1
------------------------------------------------------------

Build time:   2023-04-21 12:31:26 UTC
Revision:     1cf537a851c635c364a4214885f8b9798051175b

Kotlin:       1.8.10
Groovy:       3.0.15
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.5 (Eclipse Adoptium 17.0.5+8)
OS:           Mac OS X 13.2.1 x86_64

Additional information

Complete error when running `./gradlew app1:quarkusDev`
2023-08-26 13:38:06,883 INFO  [io.qua.dep.dev.IsolatedDevModeMain] (main) Attempting to start live reload endpoint to recover from previous Quarkus startup failure
> :ap2023-08-26 13:38:07,507 ERROR [io.qua.dep.dev.IsolatedDevModeMain] (main) Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
        [error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: jakarta.enterprise.inject.spi.DeploymentException: jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.core.kt.ConfigService and qualifiers [@Default]
        - java member: org.acme.app1.App1Controller#config
        - declared on CLASS bean [types=[org.acme.app1.App1Controller, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.app1.App1Controller]
        at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1447)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:311)
        at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:158)
        at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:469)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:858)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:282)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
        at java.base/java.lang.Thread.run(Thread.java:833)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.core.kt.ConfigService and qualifiers [@Default]
        - java member: org.acme.app1.App1Controller#config
        - declared on CLASS bean [types=[org.acme.app1.App1Controller, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.app1.App1Controller]
        at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:477)
        at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:624)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:299)
        ... 13 more

        at io.quarkus.runner.bootstrap.AugmentActionImpl.runAugment(AugmentActionImpl.java:336)
        at io.quarkus.runner.bootstrap.AugmentActionImpl.createInitialRuntimeApplication(AugmentActionImpl.java:253)
        at io.quarkus.runner.bootstrap.AugmentActionImpl.createInitialRuntimeApplication(AugmentActionImpl.java:60)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.firstStart(IsolatedDevModeMain.java:82)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:423)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:55)
        at io.quarkus.bootstrap.app.CuratedApplication.runInCl(CuratedApplication.java:138)
        at io.quarkus.bootstrap.app.CuratedApplication.runInAugmentClassLoader(CuratedApplication.java:93)
        at io.quarkus.deployment.dev.DevModeMain.start(DevModeMain.java:131)
        at io.quarkus.deployment.dev.DevModeMain.main(DevModeMain.java:62)
Caused by: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
        [error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: jakarta.enterprise.inject.spi.DeploymentException: jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.core.kt.ConfigService and qualifiers [@Default]
        - java member: org.acme.app1.App1Controller#config
        - declared on CLASS bean [types=[org.acme.app1.App1Controller, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.app1.App1Controller]
        at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1447)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:311)
        at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:158)
        at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:469)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:858)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:282)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
        at java.base/java.lang.Thread.run(Thread.java:833)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.core.kt.ConfigService and qualifiers [@Default]
        - java member: org.acme.app1.App1Controller#config
        - declared on CLASS bean [types=[org.acme.app1.App1Controller, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.app1.App1Controller]
        at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:477)
        at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:624)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:299)
        ... 13 more

        at io.quarkus.builder.Execution.run(Execution.java:123)
        at io.quarkus.builder.BuildExecutionBuilder.execute(BuildExecutionBuilder.java:79)
        at io.quarkus.deployment.QuarkusAugmentor.run(QuarkusAugmentor.java:160)
        at io.quarkus.runner.bootstrap.AugmentActionImpl.runAugment(AugmentActionImpl.java:332)
        ... 9 more
Caused by: jakarta.enterprise.inject.spi.DeploymentException: jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.core.kt.ConfigService and qualifiers [@Default]
        - java member: org.acme.app1.App1Controller#config
        - declared on CLASS bean [types=[org.acme.app1.App1Controller, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.app1.App1Controller]
        at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1447)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:311)
        at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:158)
        at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:469)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:568)
        at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:858)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:282)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
        at java.base/java.lang.Thread.run(Thread.java:833)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.acme.core.kt.ConfigService and qualifiers [@Default]
        - java member: org.acme.app1.App1Controller#config
        - declared on CLASS bean [types=[org.acme.app1.App1Controller, java.lang.Object], qualifiers=[@Default, @Any], target=org.acme.app1.App1Controller]
        at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:477)
        at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:624)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:299)
        ... 13 more

I have tried to inspect the generated jandex file in core-lib (using a jbang script) and it looks OK (my classes and its annotation are present)

Script to inspect the jandex index
///usr/bin/env jbang "$0" "$@" ; exit $?

//DEPS io.smallrye:jandex:3.1.2
//JAVA 17

import java.io.FileInputStream;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.Index;
import org.jboss.jandex.IndexReader;

public class JandexMain {
    public static void main(String[] args) throws Exception {
        try (FileInputStream input = new FileInputStream("core-lib/build/resources/main/META-INF/jandex.idx")) {
            IndexReader reader = new IndexReader(input);
            Index index = reader.read();

            // java.util.Collection<ClassInfo> knownClasses = index.getKnownClasses();
            // System.out.println(knownClasses.size());
            // for (ClassInfo classInfo : knownClasses) {
            //     System.out.println(classInfo.name());
            // }

            System.out.println("--- org.acme.core.CoreProcessor");
            ClassInfo reportingService = index.getClassByName("org.acme.core.CoreProcessor");
            for (AnnotationInstance a : reportingService.annotations()) {
                System.out.println(a.name());
            }

            System.out.println("--- org.acme.core.kt.ConfigService");
            ClassInfo foo = index.getClassByName("org.acme.core.kt.ConfigService");
            for (AnnotationInstance a : foo.annotations()) {
                System.out.println(a.name());
            }
        }
    }
}

Also when look at the trace logs of the Annotation Transformers (see docs), configured with:

quarkus.log.category."io.quarkus.arc.processor".min-level=TRACE
quarkus.log.category."io.quarkus.arc.processor".level=TRACE

I see only the line for the class written in java:

2023-08-26 13:56:06,883 TRACE [io.qua.arc.pro.BeanDeployment] (build-17) Created CLASS bean [types=[java.lang.Object, org.acme.core.CoreProcessor], qualifiers=[@Default, @Any], target=org.acme.core.CoreProcessor]

Nothing for the class written in Kotin, so it makes sense that it can not be injected later on.

@jmini jmini added the kind/bug Something isn't working label Aug 26, 2023
@quarkus-bot quarkus-bot bot added area/devmode area/gradle Gradle area/jbang Issues related to when using jbang.dev with Quarkus area/kotlin labels Aug 26, 2023
@quarkus-bot
Copy link

quarkus-bot bot commented Aug 26, 2023

/cc @evanchooly (kotlin), @geoand (kotlin), @glefloch, @maxandersen (jbang), @quarkusio/devtools (jbang)

@jmini
Copy link
Contributor Author

jmini commented Aug 27, 2023

Some more inputs to narrow the problem down:

if we look at the *.class in the core-lib project:

core-lib/build/classes/java/main/org/acme/core/TimestampMessage.class
core-lib/build/classes/java/main/org/acme/core/CoreProcessor.class
core-lib/build/classes/java/main/org/acme/core/CoreMessage.class
core-lib/build/classes/kotlin/main/org/acme/core/kt/ConfigService.class

We see that there are 2 folders:

  • build/classes/java
  • build/classes/kotlin

I have the feeling that Quarkus is only looking at build/classes/java


I didn't look yet at how this is implemented, but when you ask Gradle (inside a gradle task/plugin) about the output of the main sourceSet:

        JavaPluginExtension extension = project.getExtensions().findByType(JavaPluginExtension.class);
        SourceSet mainSourceSet = extension.getSourceSets().getByName("main");
        for (File f : mainSourceSet.getOutput().getFiles()) {
            System.out.println(f);
        }

you see the different folders at output of the main source-set:

  • core-lib/build/classes/java/main,
  • core-lib/build/classes/kotlin/main,
  • core-lib/build/resources/main

See jmini/quarkus_issue35577@d9f765e that adds a printInfo task to the gradle modules.


An other experiment to work-around this issue:

When I add a task that copy the content of build/classes/kotlin into build/classes/java with an additional task: copyReportKtClasses (see jmini/quarkus_issue35577@02c7b6b)

Then it seems that quarkus dev-mode is working correctly:

./gradlew app1:quarkusDev

I also had to:

  • Make quarkusDependenciesBuild depends on my new task
  • Add a duplicates strategy management for the jar task since now when the jar is built, all the kotlin classes are duplicates

And of course for gradle this strategy is really wrong, since it break all the input/output concept. And the compile task will not be UP_TO_DATE since my copy task is changing the output of the compile-java task.

@gsmet
Copy link
Member

gsmet commented Aug 27, 2023

@jmini maybe you can have a look at what the Gradle plugin is doing and provide a fix? That would be helpful.

@jmini
Copy link
Contributor Author

jmini commented Aug 27, 2023

I did not dig into that part of the code yet. :-)

A very naive search pointed to this test:

@Test
public void shouldReturnMultipleOutputSourceDirectories() {
Project project = ProjectBuilder.builder().build();
project.getPluginManager().apply(QuarkusPlugin.ID);
project.getPluginManager().apply("java");
project.getPluginManager().apply("scala");
final QuarkusPluginExtension extension = project.getExtensions().getByType(QuarkusPluginExtension.class);
final Set<File> outputSourceDirs = extension.combinedOutputSourceDirs();
assertThat(outputSourceDirs).hasSize(4);
assertThat(outputSourceDirs).contains(new File(project.getBuildDir(), "classes/java/main"),
new File(project.getBuildDir(), "classes/java/test"),
new File(project.getBuildDir(), "classes/scala/main"),
new File(project.getBuildDir(), "classes/scala/test"));
}

Which make me think my use case should be supported... But probably this is a complete different step and not related to my issue.


Do you have some implementation details/documentation about how the gradle plugin is working (which steps create which output and what is the difference between devMode and production build)?

I would like to narrow down the part that is not working.

Maybe the issue is in the GradleApplicationModelBuilder, but I am not sure how to continue my investigations.

Should I verify if the application model is as expected? Or different between production mode and dev mode?

Should I verify if the classpath of the started application is correct? (for instance that both core-lib/build/classes/java/main and core-lib/build/classes/kotlin/main are part of the classpath).
I have tried to do a jinfo <pid> on the process started by gradle to serve 8080.

The classpath seems only to be app1/build/app1-dev.jar

Can the application model app1/build/tmp/quarkusDev/quarkus-app-model.dat be investigated (pretty print to display it as text)?

@jmini
Copy link
Contributor Author

jmini commented Aug 28, 2023

Can the application model be investigated?

I started a jbang script to do this: QuarkusAppMain.java

It could probably be improved to display the values in a nice way, but I think this is enough to already see that gradle did not create the app model correctly.

When I deserialized the quarkus-app-model.dat into appModel and check the content of appModel.getDependencies(), the first item in that collection is:

org.acme:core-lib::jar:1.0.0-SNAPSHOT[paths: /<quarkus_issue35577 path>/core-lib/build/classes/java/main;/<quarkus_issue35577 path>/core-lib/build/resources/main;] io.quarkus.bootstrap.workspace.DefaultWorkspaceModule$Builder@53bd815b

I think the build/classes/kotlin/main path should be part of the PathCollection returned by getResolvedPaths() of the ResolvedDependency instance corresponding to the core-lib dependency.

@glefloch
Copy link
Member

@jmini yes you are right, the path build/classes/kotlin/main is missing. I'm looking on how we could detect the kotlin folder to add it.

@jmini
Copy link
Contributor Author

jmini commented Aug 31, 2023

The workaround I proposed couple of days ago is not really working in all cases.

I did not find the correct moment where copyReportKtClasses should run. I am now at:

//Workaround for https://github.com/quarkusio/quarkus/issues/35577:
def copyReportKtClassesTask = tasks.register('copyReportKtClasses', Copy) {
    dependsOn tasks.named('compileKotlin')
    from layout.buildDirectory.file('classes/kotlin')
    into layout.buildDirectory.file('classes/java/')
}
jar {
   duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
}
tasks.named("compileJava") {
    dependsOn copyReportKtClassesTask
}
//End of Workaround for https://github.com/quarkusio/quarkus/issues/35577

This works in the reproducer project:
https://github.com/jmini/quarkus_issue35577

But in our real application I am still having hard time to find why it doesn't work. The UnsatisfiedResolutionException is still present.

@jmini
Copy link
Contributor Author

jmini commented Sep 11, 2023

I did some debugging into the issue.

Breakpoint is at maybeConfigureKotlinJvmCompile(..):

private static void maybeConfigureKotlinJvmCompile(Project project, FileCollection allClassesDirs,
List<SourceDir> sourceDirs) {
// This "try/catch" is needed because of the way the "quarkus-cli" Gradle tests work. Without it, the tests fail.
try {
Class.forName("org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile");
doConfigureKotlinJvmCompile(project, allClassesDirs, sourceDirs);
} catch (ClassNotFoundException e) {
// ignore
}
}

When we are building the model for :app1 when we are collecting the dependencies.
So project instance corresponds to project ':core-lib'

Stack at breakpoint

Thread [Execution worker Thread 4] (Suspended (entry into method maybeConfigureKotlinJvmCompile in GradleApplicationModelBuilder))	
	GradleApplicationModelBuilder.maybeConfigureKotlinJvmCompile(Project, FileCollection, List<SourceDir>) line: 527	
	GradleApplicationModelBuilder.initProjectModule(Project, WorkspaceModule$Mutable, SourceSet, String) line: 487	
	GradleApplicationModelBuilder.initProjectModuleAndBuildPaths(Project, ResolvedArtifact, ApplicationModelBuilder, ResolvedDependencyBuilder, PathList$Builder, String, boolean) line: 403	
	GradleApplicationModelBuilder.collectDependencies(ResolvedDependency, boolean, Project, Set<File>, Set<ArtifactKey>, ApplicationModelBuilder, Mutable, byte) line: 337	
	GradleApplicationModelBuilder.lambda$collectDependencies$4(boolean, Project, Set, ApplicationModelBuilder, WorkspaceModule$Mutable, ResolvedDependency) line: 243	
	0x000000080186d2b8.accept(Object) line: not available	
	LinkedHashSet<E>(Iterable<T>).forEach(Consumer<? super T>) line: 75	
	GradleApplicationModelBuilder.collectDependencies(ResolvedConfiguration, boolean, Project, ApplicationModelBuilder, WorkspaceModule$Mutable) line: 242	
	GradleApplicationModelBuilder.buildAll(String, ModelParameter, Project) line: 123	
	ToolingUtils.create(Project, ModelParameter) line: 74	
	ToolingUtils.create(Project, LaunchMode) line: 70	
	QuarkusPluginExtension_Decorated(QuarkusPluginExtension).getApplicationModel(LaunchMode) line: 182	
	QuarkusGenerateCode_Decorated(QuarkusGenerateCode).generateCode() line: 104	
	NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
	NativeMethodAccessorImpl.invoke(Object, Object[]) line: 77	
	DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43	
	Method.invoke(Object, Object...) line: 568	
	JavaMethod<T,R>.invoke(T, Object...) line: 125	
	StandardTaskAction.doExecute(Task, String) line: 58	
	StandardTaskAction.execute(Task) line: 51	
	StandardTaskAction.execute(Object) line: 29	
	TaskExecution$3.run(BuildOperationContext) line: 242	
	DefaultBuildOperationRunner$1.execute(RunnableBuildOperation, BuildOperationContext) line: 29	
	DefaultBuildOperationRunner$1.execute(BuildOperation, BuildOperationContext) line: 26	
	DefaultBuildOperationRunner$2.execute(BuildOperationDescriptor, BuildOperationState, BuildOperationState, ReadableBuildOperationContext, BuildOperationExecutionListener) line: 66	
	DefaultBuildOperationRunner$2.execute(BuildOperationDescriptor, BuildOperationState, BuildOperationState, DefaultBuildOperationRunner$ReadableBuildOperationContext, DefaultBuildOperationRunner$BuildOperationExecutionListener) line: 59	
	DefaultBuildOperationRunner.execute(Builder, BuildOperationState, BuildOperationExecution<O>) line: 157	
	DefaultBuildOperationRunner.execute(O, BuildOperationWorker<O>, BuildOperationState) line: 59	
	DefaultBuildOperationRunner.run(RunnableBuildOperation) line: 47	
	DefaultBuildOperationExecutor.run(RunnableBuildOperation) line: 68	
	TaskExecution.executeAction(String, TaskInternal, InputChangesAwareTaskAction, InputChangesInternal, boolean) line: 227	
	TaskExecution.executeActions(TaskInternal, InputChangesInternal) line: 210	
	TaskExecution.executeWithPreviousOutputFiles(InputChangesInternal) line: 193	
	TaskExecution.execute(UnitOfWork$ExecutionRequest) line: 166	
	ExecuteStep<C>.executeInternal(UnitOfWork, InputChangesContext) line: 105	
	ExecuteStep<C>.access$000(UnitOfWork, InputChangesContext) line: 44	
	ExecuteStep$1.call(BuildOperationContext) line: 59	
	ExecuteStep$1.call(BuildOperationContext) line: 56	
	DefaultBuildOperationRunner$CallableBuildOperationWorker<T>.execute(CallableBuildOperation<T>, BuildOperationContext) line: 204	
	DefaultBuildOperationRunner$CallableBuildOperationWorker<T>.execute(BuildOperation, BuildOperationContext) line: 199	
	DefaultBuildOperationRunner$2.execute(BuildOperationDescriptor, BuildOperationState, BuildOperationState, ReadableBuildOperationContext, BuildOperationExecutionListener) line: 66	
	DefaultBuildOperationRunner$2.execute(BuildOperationDescriptor, BuildOperationState, BuildOperationState, DefaultBuildOperationRunner$ReadableBuildOperationContext, DefaultBuildOperationRunner$BuildOperationExecutionListener) line: 59	
	DefaultBuildOperationRunner.execute(Builder, BuildOperationState, BuildOperationExecution<O>) line: 157	
	DefaultBuildOperationRunner.execute(O, BuildOperationWorker<O>, BuildOperationState) line: 59	
	DefaultBuildOperationRunner.call(CallableBuildOperation<T>) line: 53	
	DefaultBuildOperationExecutor.call(CallableBuildOperation<T>) line: 73	
	ExecuteStep<C>.execute(UnitOfWork, C) line: 56	
	ExecuteStep<C>.execute(UnitOfWork, Context) line: 44	
	RemovePreviousOutputsStep<C,R>.execute(UnitOfWork, C) line: 67	
	RemovePreviousOutputsStep<C,R>.execute(UnitOfWork, Context) line: 37	
	CancelExecutionStep<C,R>.execute(UnitOfWork, C) line: 41	
	TimeoutStep<C,R>.executeWithoutTimeout(UnitOfWork, C) line: 74	
	TimeoutStep<C,R>.execute(UnitOfWork, C) line: 55	
	CreateOutputsStep<C,R>.execute(UnitOfWork, C) line: 50	
	CreateOutputsStep<C,R>.execute(UnitOfWork, Context) line: 28	
	CaptureStateAfterExecutionStep<C>.executeDelegateBroadcastingChanges(UnitOfWork, C) line: 100	
	CaptureStateAfterExecutionStep<C>.execute(UnitOfWork, C) line: 72	
	CaptureStateAfterExecutionStep<C>.execute(UnitOfWork, Context) line: 50	
	ResolveInputChangesStep<C,R>.execute(UnitOfWork, C) line: 40	
	ResolveInputChangesStep<C,R>.execute(UnitOfWork, Context) line: 29	
	BuildCacheStep.executeWithoutCache(UnitOfWork, IncrementalChangesContext) line: 166	
	BuildCacheStep.lambda$execute$1(UnitOfWork, IncrementalChangesContext, CachingState$Disabled) line: 70	
	0x00000008015c53f8.apply(Object) line: not available	
	Either$Right<L,R>.fold(Function<? super L,? extends U>, Function<? super R,? extends U>) line: 175	
	CachingState.fold(Function<Enabled,T>, Function<Disabled,T>) line: 59	
	BuildCacheStep.execute(UnitOfWork, IncrementalChangesContext) line: 68	
	BuildCacheStep.execute(UnitOfWork, Context) line: 46	
	StoreExecutionStateStep<C,R>.execute(UnitOfWork, C) line: 36	
	StoreExecutionStateStep<C,R>.execute(UnitOfWork, Context) line: 25	
	RecordOutputsStep<C,R>.execute(UnitOfWork, C) line: 36	
	RecordOutputsStep<C,R>.execute(UnitOfWork, Context) line: 22	
	SkipUpToDateStep<C>.executeBecause(UnitOfWork, ImmutableList<String>, C) line: 91	
	SkipUpToDateStep<C>.lambda$execute$2(UnitOfWork, ImmutableList, IncrementalChangesContext) line: 55	
	0x00000008015c4f88.get() line: not available	
	Optional<T>.orElseGet(Supplier<? extends T>) line: 364	
	SkipUpToDateStep<C>.execute(UnitOfWork, C) line: 55	
	SkipUpToDateStep<C>.execute(UnitOfWork, Context) line: 37	
	ResolveChangesStep<C,R>.execute(UnitOfWork, C) line: 65	
	ResolveChangesStep<C,R>.execute(UnitOfWork, Context) line: 36	
	MarkSnapshottingInputsFinishedStep<C,R>.execute(UnitOfWork, C) line: 37	
	MarkSnapshottingInputsFinishedStep<C,R>.execute(UnitOfWork, Context) line: 27	
	ResolveCachingStateStep<C>.execute(UnitOfWork, C) line: 76	
	ResolveCachingStateStep<C>.execute(UnitOfWork, Context) line: 37	
	ValidateStep<C,R>.execute(UnitOfWork, C) line: 94	
	ValidateStep<C,R>.execute(UnitOfWork, Context) line: 49	
	CaptureStateBeforeExecutionStep<C,R>.execute(UnitOfWork, C) line: 71	
	CaptureStateBeforeExecutionStep<C,R>.execute(UnitOfWork, Context) line: 45	
	SkipEmptyWorkStep.executeWithNonEmptySources(UnitOfWork, PreviousExecutionContext) line: 177	
	SkipEmptyWorkStep.execute(UnitOfWork, PreviousExecutionContext) line: 81	
	SkipEmptyWorkStep.execute(UnitOfWork, Context) line: 53	
	RemoveUntrackedExecutionStateStep<C,R>.execute(UnitOfWork, C) line: 32	
	RemoveUntrackedExecutionStateStep<C,R>.execute(UnitOfWork, Context) line: 21	
	MarkSnapshottingInputsStartedStep<C,R>.execute(UnitOfWork, C) line: 38	
	LoadPreviousExecutionStateStep<C,R>.execute(UnitOfWork, C) line: 36	
	LoadPreviousExecutionStateStep<C,R>.execute(UnitOfWork, Context) line: 23	
	CleanupStaleOutputsStep<C,R>.execute(UnitOfWork, C) line: 75	
	CleanupStaleOutputsStep<C,R>.execute(UnitOfWork, Context) line: 41	
	AssignWorkspaceStep<C,R>.lambda$execute$0(UnitOfWork, IdentityContext, File, ExecutionHistoryStore) line: 32	
	0x00000008015a2540.executeInWorkspace(File, ExecutionHistoryStore) line: not available	
	TaskExecution$4.withWorkspace(String, WorkspaceAction<T>) line: 287	
	AssignWorkspaceStep<C,R>.execute(UnitOfWork, C) line: 30	
	AssignWorkspaceStep<C,R>.execute(UnitOfWork, Context) line: 21	
	IdentityCacheStep<C,R>.execute(UnitOfWork, C) line: 37	
	IdentityCacheStep<C,R>.execute(UnitOfWork, Context) line: 27	
	IdentifyStep<C,R>.execute(UnitOfWork, C) line: 47	
	IdentifyStep<C,R>.execute(UnitOfWork, Context) line: 34	
	DefaultExecutionEngine$1.execute() line: 64	
	ExecuteActionsTaskExecuter.executeIfValid(TaskInternal, TaskStateInternal, TaskExecutionContext, TaskExecution) line: 146	
	ExecuteActionsTaskExecuter.execute(TaskInternal, TaskStateInternal, TaskExecutionContext) line: 135	
	FinalizePropertiesTaskExecuter.execute(TaskInternal, TaskStateInternal, TaskExecutionContext) line: 46	
	ResolveTaskExecutionModeExecuter.execute(TaskInternal, TaskStateInternal, TaskExecutionContext) line: 51	
	SkipTaskWithNoActionsExecuter.execute(TaskInternal, TaskStateInternal, TaskExecutionContext) line: 57	
	SkipOnlyIfTaskExecuter.execute(TaskInternal, TaskStateInternal, TaskExecutionContext) line: 74	
	CatchExceptionTaskExecuter.execute(TaskInternal, TaskStateInternal, TaskExecutionContext) line: 36	
	EventFiringTaskExecuter$1.executeTask(BuildOperationContext) line: 77	
	EventFiringTaskExecuter$1.call(BuildOperationContext) line: 55	
	EventFiringTaskExecuter$1.call(BuildOperationContext) line: 52	
	DefaultBuildOperationRunner$CallableBuildOperationWorker<T>.execute(CallableBuildOperation<T>, BuildOperationContext) line: 204	
	DefaultBuildOperationRunner$CallableBuildOperationWorker<T>.execute(BuildOperation, BuildOperationContext) line: 199	
	DefaultBuildOperationRunner$2.execute(BuildOperationDescriptor, BuildOperationState, BuildOperationState, ReadableBuildOperationContext, BuildOperationExecutionListener) line: 66	
	DefaultBuildOperationRunner$2.execute(BuildOperationDescriptor, BuildOperationState, BuildOperationState, DefaultBuildOperationRunner$ReadableBuildOperationContext, DefaultBuildOperationRunner$BuildOperationExecutionListener) line: 59	
	DefaultBuildOperationRunner.execute(Builder, BuildOperationState, BuildOperationExecution<O>) line: 157	
	DefaultBuildOperationRunner.execute(O, BuildOperationWorker<O>, BuildOperationState) line: 59	
	DefaultBuildOperationRunner.call(CallableBuildOperation<T>) line: 53	
	DefaultBuildOperationExecutor.call(CallableBuildOperation<T>) line: 73	
	EventFiringTaskExecuter.execute(TaskInternal, TaskStateInternal, TaskExecutionContext) line: 52	
	LocalTaskNodeExecutor.execute(Node, NodeExecutionContext) line: 42	
	DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(Node) line: 337	
	DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(Object) line: 324	
	DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(Node) line: 317	
	DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(Object) line: 303	
	DefaultPlanExecutor$ExecutorWorker.execute(Object, WorkSource<Object>, Action<Object>) line: 463	
	DefaultPlanExecutor$ExecutorWorker.run() line: 380	
	ExecutorPolicy$CatchAndRecordFailures.onExecute(Runnable) line: 64	
	ManagedExecutorImpl$1.run() line: 49	
	ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1136	
	ThreadPoolExecutor$Worker.run() line: 635	
	Thread.run() line: 833	

From a gradle task point of view we are at starting the quarkusGenerateCodeDev task in the :app1 module:

> Task :app1:processResources
> Task :core-lib:processResources
> Task :core-lib:quarkusGenerateCode
> Task :core-lib:quarkusGenerateCodeDev
> Task :app1:processTestResources NO-SOURCE
> Task :app1:compileQuarkus-test-generated-sourcesKotlin NO-SOURCE
> Task :app1:compileQuarkusTestGeneratedSourcesJava NO-SOURCE
> Task :core-lib:compileKotlin
> Task :core-lib:compileJava
> Task :core-lib:classes
> Task :core-lib:jandex
> Task :core-lib:jar
> Task :app1:quarkusGenerateCode
> Task :app1:quarkusGenerateCodeDev

First finding:

The way maybeConfigureKotlinJvmCompile(..) requires the module :app1 to have the kotlin plugin installed.

Otherwise the method is skipped because of the try-catch:

Print of the Stacktrace of the exception in the catch:

``` java.lang.ClassNotFoundException: org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445) at org.gradle.internal.classloader.VisitableURLClassLoader$InstrumentingVisitableURLClassLoader.findClass(VisitableURLClassLoader.java:186) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Class.java:375) at io.quarkus.gradle.tooling.GradleApplicationModelBuilder.maybeConfigureKotlinJvmCompile(GradleApplicationModelBuilder.java:527) at io.quarkus.gradle.tooling.GradleApplicationModelBuilder.initProjectModule(GradleApplicationModelBuilder.java:487) at io.quarkus.gradle.tooling.GradleApplicationModelBuilder.initProjectModuleAndBuildPaths(GradleApplicationModelBuilder.java:403) at io.quarkus.gradle.tooling.GradleApplicationModelBuilder.collectDependencies(GradleApplicationModelBuilder.java:337) at io.quarkus.gradle.tooling.GradleApplicationModelBuilder.lambda$collectDependencies$4(GradleApplicationModelBuilder.java:243) at java.base/java.lang.Iterable.forEach(Iterable.java:75) at io.quarkus.gradle.tooling.GradleApplicationModelBuilder.collectDependencies(GradleApplicationModelBuilder.java:242) at io.quarkus.gradle.tooling.GradleApplicationModelBuilder.buildAll(GradleApplicationModelBuilder.java:123) at io.quarkus.gradle.tooling.ToolingUtils.create(ToolingUtils.java:74) at io.quarkus.gradle.tooling.ToolingUtils.create(ToolingUtils.java:70) at io.quarkus.gradle.extension.QuarkusPluginExtension.getApplicationModel(QuarkusPluginExtension.java:181) at io.quarkus.gradle.tasks.QuarkusDev.newLauncher(QuarkusDev.java:447) at io.quarkus.gradle.tasks.QuarkusDev.startDev(QuarkusDev.java:342) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:125) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51) at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29) at org.gradle.api.internal.tasks.execution.TaskExecution$3.run(TaskExecution.java:242) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29) at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47) at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68) at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:227) at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:210) at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:193) at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:166) at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:105) at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:44) at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:59) at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:56) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73) at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:56) at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:44) at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:67) at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:37) at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:41) at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:74) at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55) at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:50) at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:28) at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.executeDelegateBroadcastingChanges(CaptureStateAfterExecutionStep.java:100) at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:72) at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:50) at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:40) at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:29) at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:166) at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:70) at org.gradle.internal.Either$Right.fold(Either.java:175) at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:59) at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:68) at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:46) at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:36) at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:25) at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:36) at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:22) at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:91) at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:55) at java.base/java.util.Optional.orElseGet(Optional.java:364) at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:55) at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:37) at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:65) at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:36) at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37) at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27) at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:76) at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:37) at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:94) at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:49) at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:71) at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:45) at org.gradle.internal.execution.steps.SkipEmptyWorkStep.executeWithNonEmptySources(SkipEmptyWorkStep.java:177) at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:81) at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:53) at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:32) at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:21) at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38) at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36) at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23) at org.gradle.internal.execution.steps.CleanupStaleOutputsStep.execute(CleanupStaleOutputsStep.java:75) at org.gradle.internal.execution.steps.CleanupStaleOutputsStep.execute(CleanupStaleOutputsStep.java:41) at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:32) at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:287) at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30) at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:21) at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37) at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27) at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:47) at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:34) at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:64) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:146) at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:135) at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46) at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51) at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57) at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74) at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157) at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73) at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52) at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:337) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:324) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:317) at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303) at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:463) at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:380) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:49) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:833) ```

In the reproducer only :core-lib was a Kotlin project.
But the kotlin plugin can be added to the :app1 module as well.

Second finding:

Even after adding the kotlin plugin in :app1, the quarkus gradle plugin is not working.

The reason is that when you ask for tasks withType(org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile.class) of project :core-lib inside the project :app1 the size of the returned collection is 0.

Verifier checkout the branch analysis of the reproducer and compare the output of:

./gradlew printInfo1

This task is defined in the :core-lib module

--> withType(org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile.class) returns a task for the :core-lib module.

and:

./gradlew printInfo2

This task is defined in the :app1 module, accessing the tasks of the other :core-lib module.

--> withType(org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile.class) finds nothing in the :core-lib module accessed from :app1


@aloubyansky it is a place you seems to know (from issue #20755)
@glefloch what do you think?

@jmini
Copy link
Contributor Author

jmini commented Sep 11, 2023

At the end I am not sure why the io.quarkus.bootstrap.workspace.ArtifactSources is defined like this with io.quarkus.bootstrap.workspace.SourceDir expecting:

  • srcDir
  • destinationDir

Because the cardinality does not match, with the Gradle sourceSet model.

If you take a pure java project with 2 java source folders:

.
├── build.gradle
├── settings.gradle
├── src-1
│   └── Hello.java
└── src-2
    └── World.java

with this build.gradle:

plugins {
    id 'java'
}

sourceSets {
    main {
        java {
            srcDirs 'src-1', 'src-2'
        }
    }
}

The source set 'main' defined in Gradle has one destination folder build/classes/java/main but 2 source folders.

build/classes/
└── java
    └── main
        ├── Hello.class
        └── World.class

If we consider the source set 'main' of :core-lib in the reproducer, we could also consider that we have 2 destinations folders:

  • build/classes/java/main
  • build/classes/kotlin/main

Created by multiple source folders and multiple tasks (which is the information available in the ConfigurableFileCollection without too much hacks as it is now)


My current work-around is to take the allClassesDirs and make sure there is at least one entry in the sourceDirs of the quarkus model (where the classes-dir corresponds to the destinationDir in the entry). The srcDir of that entry does not really matter.

See my proposal: jmini@a426765 and let me know if I should file that as a PR.

@jmini
Copy link
Contributor Author

jmini commented Sep 18, 2023

As discussed on Zulip:

I am wondering if the quarkus application model (io.quarkus.bootstrap.workspace.ArtifactSources and io.quarkus.bootstrap.workspace.SourceDir) is really compatible with the gradle model of SourceSet


The quarkus model:

  • For ArtifactSources you have to provide:
    • a collection of SourceDir for sources
    • a collection of SourceDir for resources
  • and SourceDir is expecting:
    • a source directory SourceDir#getDir()
    • a destination directory SourceDir#getOutputDir()
    • internally, there is already more flexibility since the source and the destination items are modeled as io.quarkus.paths.PathTree which gives more flexibility.

The Gradle SourceSet model:

  • You have multiple sources folders (you can distinguish between sources and resources)
  • You have multiple classes folder
  • You have multiple tasks creating the output "builtBy"

Example with a simple project with the kotlin extension (link to generate on code.quarkus.io). If I inspect the main SourceSet I have:

all sources srcDirs: 
- /src/main/resources
- /src/main/java
- /build/classes/java/quarkus-generated-sources
- /build/classes/java/quarkus-generated-sources/grpc
- /build/classes/java/quarkus-generated-sources/avdl
- /build/classes/java/quarkus-generated-sources/avpr
- /build/classes/java/quarkus-generated-sources/avsc
- /src/main/kotlin

resources srcDirs: 
- /src/main/resources

ouput ClassesDir: 
- /build/classes/java/main
- /build/classes/kotlin/main

ouput ClassesDir builtBy: 
- task ':compileKotlin'
- task ':compileJava'

And the are more complicated stuff that can be done in gradle (like more compile plugins or having a task that generate resources directly and register it to the main source set output with a builtBy declaration)


My feeling is that because the Quarkus model is built like this, it is really difficult to map the Gradle model into the Quarkus one.

And this makes the implementation of GradleApplicationModelBuilder#initProjectModule(Project, Mutable, SourceSet, String) really complex:

private static void initProjectModule(Project project, WorkspaceModule.Mutable module, SourceSet sourceSet,
String classifier) {
if (sourceSet == null) {
return;
}
final FileCollection allClassesDirs = sourceSet.getOutput().getClassesDirs();
// some plugins do not add source directories to source sets and they may be missing from sourceSet.getAllJava()
// see https://github.com/quarkusio/quarkus/issues/20755
final List<SourceDir> sourceDirs = new ArrayList<>(1);
project.getTasks().withType(AbstractCompile.class,
t -> configureCompileTask(t.getSource(), t.getDestinationDirectory(), allClassesDirs, sourceDirs, t));
maybeConfigureKotlinJvmCompile(project, allClassesDirs, sourceDirs);
final LinkedHashMap<File, Path> resourceDirs = new LinkedHashMap<>(1);
final File resourcesOutputDir = sourceSet.getOutput().getResourcesDir();
project.getTasks().withType(ProcessResources.class, t -> {
if (!t.getEnabled()) {
return;
}
final FileCollection source = t.getSource();
if (source.isEmpty()) {
return;
}
if (!t.getDestinationDir().equals(resourcesOutputDir)) {
return;
}
final Path destDir = t.getDestinationDir().toPath();
source.getAsFileTree().visit(a -> {
// we are looking for the root dirs containing sources
if (a.getRelativePath().getSegments().length == 1) {
final File srcDir = a.getFile().getParentFile();
resourceDirs.put(srcDir, destDir);
}
});
});
// there could be a task generating resources
if (resourcesOutputDir.exists() && resourceDirs.isEmpty()) {
sourceSet.getResources().getSrcDirs()
.forEach(srcDir -> resourceDirs.put(srcDir, resourcesOutputDir.toPath()));
}
final List<SourceDir> resources = new ArrayList<>(resourceDirs.size());
for (Map.Entry<File, Path> e : resourceDirs.entrySet()) {
resources.add(new DefaultSourceDir(e.getKey().toPath(), e.getValue()));
}
module.addArtifactSources(new DefaultArtifactSources(classifier, sourceDirs, resources));
}

The complexity of this method (checking each task by type AbstractCompile and KotlinJvmCompile and checking the their input and outputs visitor methods) is because the current implementation tries to obtain from Gradle information that does not really exists (this is not how gradle works) to create the the Quarkus model.


Some notes on the Gradle API:

Simple printInfo task to inspect the main source-set

tasks.register('printInfo') {
    doLast {
        def mainSourceSet = sourceSets.main
        println(mainSourceSet)

        println("all sources srcDirs: ")
        mainSourceSet.getAllSource().getSrcDirs().each {
            println("- " + it.getAbsolutePath().substring(project.getRootProject().getProjectDir().getAbsolutePath().length() + 1))
        }
        println("resources srcDirs: ")
        mainSourceSet.getResources().getSrcDirs().each {
            println("- " + it.getAbsolutePath().substring(project.getRootProject().getProjectDir().getAbsolutePath().length() + 1))
        }
        println("ouput ClassesDir: ")
        mainSourceSet.getOutput().getClassesDirs().each {
            println("- " + it.getAbsolutePath().substring(project.getRootProject().getProjectDir().getAbsolutePath().length() + 1))
        }
        println("ouput ClassesDir builtBy: ")
        (mainSourceSet.getOutput().getClassesDirs() as org.gradle.api.file.ConfigurableFileCollection).getBuiltBy().each {
            println("- " + it.get())
        }
    }
}

@andreas-eberle
Copy link
Contributor

Are there any updates on this issue? This is quite a pain in a bigger project.

@jmini
Copy link
Contributor Author

jmini commented Nov 13, 2023

Personally I would love to contribute a patch, but I need some guidance:

  • to understand in which direction it should go (small hack or big change)
  • where to put the integration test

I am not investing more time into this until I get some feedback from the committers responsible for the gradle plugin.

@geoand
Copy link
Contributor

geoand commented Nov 13, 2023

@aloubyansky @glefloch @snazy ^

@aloubyansky
Copy link
Member

Personally I would love to contribute a patch, but I need some guidance:

  • to understand in which direction it should go (small hack or big change)

We might want to consider both, depending on the scope of the big change and how the small hack would look like.
If a small hack looks like a quick reasonable change that would unblock people, let's consider that first and work on a bigger and proper change to fix it. I'll be interested in collaborating on that.

  • where to put the integration test

Gradle ITs are in https://github.com/quarkusio/quarkus/tree/main/integration-tests/gradle. Given that the Quarkus project is Maven a project, Gradle ITs are actually launched from Maven. Meaning to run Gradle ITs from that module you could execute

mvn clean test to run all the tests or
mvn clean test -Dtest=<test-class-name> to run a specific test.

The test projects are located under main/resources and copied to target/classes and then also pre-processed as part of the test setup to set the appropriate Quarkus version matching the one in the main project.

I am not investing more time into this until I get some feedback from the committers responsible for the gradle plugin.

@snazy
Copy link
Contributor

snazy commented Nov 13, 2023

quarkusDev is quite different. The current implementation basically defers all compilation to Quarkus code. This is (sadly) often wrong in (non trivial) Gradle projects that refer to non-standard project dependencies. By "non-standard" I mean dependencies to projects that are not Java and/or depend on a "non standard" configuration (like referencing a shadow-jar).

To fix that, there's a bunch of code that needs to be touched. IMO Gradle's continuous build functionality should be used to build the project including its dependencies. The Quarkus Dev code parts need to be changed to only reload changed parts but never actually build code.

I've started some work here, but haven't found time to continue on that (the code in that branch is hacky & messy). IIRC it could at least figure our the dependencies in a better way - but all the changes to the Quarkus Dev code are still missing. I don't mind if you want to pull some stuff from there.

@aloubyansky
Copy link
Member

Right, Gradle continuous build would behind quarkusDev be the ultimate longer term goal.

@jmini
Copy link
Contributor Author

jmini commented Nov 14, 2023

We might want to consider both, depending on the scope of the big change and how the small hack would look like.

@aloubyansky does jmini@a426765 qualify as something that would be accepted?

If yes I could turn my example project quarkus_issue35577 into an integration test that would be failing before my patch and green after.

EDIT: the integration test part might be tricky since there is currently no tests testing :quarkusDev

@aloubyansky
Copy link
Member

@jmini yes, i think it would be acceptable. Thanks.

@jmini
Copy link
Contributor Author

jmini commented Nov 14, 2023

There is a way to reproduce it with ./gradlew quarkusIntTest (after having added a test case to the reproducer of course)

Currently failing with:

App1Test > testHelloEndpoint() FAILED
    java.lang.RuntimeException at QuarkusTestExtension.java:639
        Caused by: java.lang.RuntimeException at AugmentActionImpl.java:336
            Caused by: io.quarkus.builder.BuildException at Execution.java:123
                Caused by: jakarta.enterprise.inject.spi.DeploymentException at BeanDeployment.java:1447
                    Caused by: jakarta.enterprise.inject.UnsatisfiedResolutionException at Beans.java:477

So I think I can use this for the integration test.

The test case MultiModuleKotlinProjectBuildTest.java with the sources multi-module-kotlin-project seems to a very good starting point for my reproducer as part of the test suite.

@aloubyansky
Copy link
Member

Sure, in case you are modifying an existing test, please make sure you are keeping the test for the original use-case. Thanks.

@maxandersen
Copy link
Member

Did we find way for contionous build not being triggered unless we had a request coming in?

@snazy
Copy link
Contributor

snazy commented Nov 15, 2023

Did we find way for contionous build not being triggered unless we had a request coming in?

Gradle notifies „us“, when builds finish, including the artifacts that have been changed. Our plugin could forward the notification to the Quarkus process. The change in Quarkus-dev would be to only reload changed artifacts, but not trigger any build actions.

jmini added a commit to jmini/quarkus that referenced this issue Nov 16, 2023
jmini added a commit to jmini/quarkus that referenced this issue Nov 16, 2023
jmini added a commit to jmini/quarkus that referenced this issue Nov 16, 2023
jmini added a commit to jmini/quarkus that referenced this issue Nov 16, 2023
@clementguillot
Copy link

Hi @jmini are you still trying to fix this issue? I'm also affected and I have time help!

@jmini
Copy link
Contributor Author

jmini commented Mar 21, 2024

@clementguillot can you check if my patch jmini@889d634 would work for you?

I can try to rebase it on head of the quarkus repo.

@clementguillot
Copy link

@jmini sure, I will try on an existing multi-module Kotlin application

@jmini
Copy link
Contributor Author

jmini commented Mar 22, 2024

I was able to rebase my patch on top of origin/main
jmini@57183eb

@jmini
Copy link
Contributor Author

jmini commented Mar 23, 2024

I was wondering why I could not reproduce the issue in the multi-module-kotlin-project integration test and I found something:

Usually the root gradle project does not contains any sources, sometimes you have sections like allprojects { } or subprojects { } to configure each of the modules. But if you apply org.jetbrains.kotlin.jvm to this root project, the issue can't be reproduced.


So despite the fact that there is no sources, if you add this to the root build.gradle:

plugins {
    id 'org.jetbrains.kotlin.jvm' version "1.9.10"
}

repositories {
    mavenCentral()
}

The issue described here can't be reproduced.

^ I see this as a valid work-around. cc: @clementguillot


🤔 I did not exactly investigated why, but adding this in the top project modifies the behavior of the compileKotlin task instance being or not an instance of org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile.

@clementguillot
Copy link

Thank you @jmini for your detailed answer. I have tested on my side and I can confirm that I'm only able to reproduce issue without plugin org.jetbrains.kotlin.jvm in root build.gradle.

Once I globally add this plugin, everything works as expected, including hot reload. Many thanks for your help!

jmini added a commit to jmini/quarkus that referenced this issue Mar 25, 2024
@andreas-eberle
Copy link
Contributor

@jmini : Did you try if it also works when you have the kotlin jvm plugin in the root gradle but mark it as apply(false)?

The reason I'm asking is that we cannot apply this on top level because it would then apply to all submodules. This however, does not work together with Kotlin Multiplatform (which we use in our monorepo for the app of the service).

@clementguillot
Copy link

@andreas-eberle it works and it fixes couple of warnings in IntelliJ and at build-time 😉

@andreas-eberle
Copy link
Contributor

True, it looks like it works for a normal Kotlin module. Now I have one more thing... :D
It doesn't work when the submodule is a Kotlin Multiplatform module... it seems then the source dirs are different again. I then get

Execution failed for task ':server:quarkusGenerateCodeDev'.
> Cannot invoke "io.quarkus.bootstrap.workspace.ArtifactSources.getSourceDirs()" because the return value of "io.quarkus.bootstrap.workspace.WorkspaceModule$Mutable.getSources(String)" is null

@pavelkryl
Copy link

pavelkryl commented May 28, 2024

Same problem here. I have a dependency on Kotlin Multiplatform module and the build is failing. Any suggestions how to workaround/fix this? I can share my project layout if interested.

@pavelkryl
Copy link

Workaround till this bug exists: I have two gradle modules. One has applied kotlin.jvm plugin, the other has applied kotlin.multiplatform. I am linking (via symbolic link) src/main/kotlin from the jvm module as src/main/commonMain into the multiplatform module. This way I am effectively sharing the exactly same source code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/devmode area/gradle Gradle area/jbang Issues related to when using jbang.dev with Quarkus area/kotlin kind/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

10 participants