Skip to content

Commit 17ea8c0

Browse files
authored
Merge pull request #47730 from aloubyansky/included-kotlin-build
Fix included Kotlin build in quarkusDev
2 parents 8bf9175 + 3d1029d commit 17ea8c0

File tree

15 files changed

+334
-34
lines changed

15 files changed

+334
-34
lines changed

devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java

+70-34
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.Objects;
1818
import java.util.Properties;
1919
import java.util.Set;
20+
import java.util.concurrent.atomic.AtomicReference;
2021

2122
import org.gradle.api.Project;
2223
import org.gradle.api.Task;
@@ -36,7 +37,7 @@
3637
import org.gradle.internal.composite.IncludedBuildInternal;
3738
import org.gradle.language.jvm.tasks.ProcessResources;
3839
import org.gradle.tooling.provider.model.ParameterizedToolingModelBuilder;
39-
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile;
40+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool;
4041

4142
import io.quarkus.bootstrap.BootstrapConstants;
4243
import io.quarkus.bootstrap.model.ApplicationModel;
@@ -60,7 +61,6 @@
6061
import io.quarkus.maven.dependency.GACT;
6162
import io.quarkus.maven.dependency.GACTV;
6263
import io.quarkus.maven.dependency.GAV;
63-
import io.quarkus.maven.dependency.ResolvedDependency;
6464
import io.quarkus.maven.dependency.ResolvedDependencyBuilder;
6565
import io.quarkus.paths.PathCollection;
6666
import io.quarkus.paths.PathList;
@@ -352,23 +352,20 @@ private void collectDependencies(org.gradle.api.artifacts.ResolvedDependency res
352352
}
353353

354354
PathCollection paths = null;
355-
if (workspaceDiscovery && a.getId().getComponentIdentifier() instanceof ProjectComponentIdentifier) {
356-
357-
Project projectDep = project.getRootProject().findProject(
358-
((ProjectComponentIdentifier) a.getId().getComponentIdentifier()).getProjectPath());
355+
if (workspaceDiscovery && a.getId().getComponentIdentifier() instanceof ProjectComponentIdentifier compId) {
356+
Project projectDep = project.getRootProject().findProject(compId.getProjectPath());
359357
SourceSetContainer sourceSets = projectDep == null ? null
360358
: projectDep.getExtensions().findByType(SourceSetContainer.class);
361359

362360
final String classifier = a.getClassifier();
363361
if (classifier == null || classifier.isEmpty()) {
364362
final IncludedBuild includedBuild = ToolingUtils.includedBuild(project.getRootProject(),
365-
((ProjectComponentIdentifier) a.getId().getComponentIdentifier()).getBuild().getName());
363+
compId.getBuild().getName());
366364
if (includedBuild != null) {
367365
final PathList.Builder pathBuilder = PathList.builder();
368366

369-
if (includedBuild instanceof IncludedBuildInternal) {
370-
projectDep = ToolingUtils.includedBuildProject((IncludedBuildInternal) includedBuild,
371-
((ProjectComponentIdentifier) a.getId().getComponentIdentifier()).getProjectPath());
367+
if (includedBuild instanceof IncludedBuildInternal ib) {
368+
projectDep = ToolingUtils.includedBuildProject(ib, compId.getProjectPath());
372369
}
373370
if (projectDep != null) {
374371
projectModule = initProjectModuleAndBuildPaths(projectDep, a, modelBuilder, depBuilder,
@@ -517,7 +514,6 @@ private static Properties readDescriptor(final Path path) {
517514

518515
private static void initProjectModule(Project project, WorkspaceModule.Mutable module, SourceSet sourceSet,
519516
String classifier) {
520-
521517
if (sourceSet == null) {
522518
return;
523519
}
@@ -570,45 +566,57 @@ private static void initProjectModule(Project project, WorkspaceModule.Mutable m
570566

571567
private static void maybeConfigureKotlinJvmCompile(Project project, FileCollection allClassesDirs,
572568
List<SourceDir> sourceDirs, SourceSet sourceSet) {
573-
// This "try/catch" is needed because of the way the "quarkus-cli" Gradle tests work. Without it, the tests fail.
574-
try {
575-
Class.forName("org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile");
576-
doConfigureKotlinJvmCompile(project, allClassesDirs, sourceDirs, sourceSet);
577-
} catch (ClassNotFoundException e) {
578-
// ignore
569+
for (var task : project.getTasks()) {
570+
if (task.getName().contains("compileKotlin") && task.getEnabled()) {
571+
int originalSourceDirsSize = sourceDirs.size();
572+
573+
// This "try/catch" is needed because of the way the "quarkus-cli" Gradle tests work. Without it, the tests fail.
574+
try {
575+
Class.forName("org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool");
576+
doConfigureKotlinJvmCompile(project, allClassesDirs, sourceDirs, sourceSet);
577+
} catch (ClassNotFoundException e) {
578+
// ignore
579+
}
580+
// if the above failed, there could still be a KotlinCompile task that's not easily discoverable
581+
if (originalSourceDirsSize == sourceDirs.size()) {
582+
final Path outputDir = getClassesOutputDir(task);
583+
if (outputDir != null && task.getInputs().getHasInputs()) {
584+
task.getInputs().getSourceFiles().getAsFileTree().visit(visitor -> {
585+
if (visitor.getRelativePath().getSegments().length == 1) {
586+
sourceDirs.add(SourceDir.of(visitor.getFile().getParentFile().toPath(), outputDir));
587+
}
588+
});
589+
}
590+
break;
591+
}
592+
}
579593
}
580594
}
581595

582596
private static void doConfigureKotlinJvmCompile(Project project, FileCollection allClassesDirs,
583597
List<SourceDir> sourceDirs, SourceSet sourceSet) {
584598
// Use KotlinJvmCompile.class in a separate method to prevent that maybeConfigureKotlinJvmCompile() runs into
585599
// a ClassNotFoundException due to actually using KotlinJvmCompile.class.
586-
project.getTasks().withType(KotlinJvmCompile.class, t -> configureCompileTask(t.getSources().getAsFileTree(),
600+
project.getTasks().withType(KotlinCompileTool.class, t -> configureCompileTask(t.getSources().getAsFileTree(),
587601
t.getDestinationDirectory(), allClassesDirs, sourceDirs, t, sourceSet));
588602
}
589603

590604
private static void configureCompileTask(FileTree sources, DirectoryProperty destinationDirectory,
591605
FileCollection allClassesDirs, List<SourceDir> sourceDirs, Task task, SourceSet sourceSet) {
592-
if (!task.getEnabled()) {
593-
return;
594-
}
595-
if (sources.isEmpty()) {
606+
if (!task.getEnabled() || sources.isEmpty()) {
596607
return;
597608
}
598-
599609
final File destDir = destinationDirectory.getAsFile().get();
600610
if (!allClassesDirs.contains(destDir)) {
601611
return;
602612
}
603-
sources.visit(a -> {
613+
sources.visit(visitor -> {
604614
// we are looking for the root dirs containing sources
605-
if (a.getRelativePath().getSegments().length == 1) {
606-
final File srcDir = a.getFile().getParentFile();
607-
608-
sourceDirs
609-
.add(new DefaultSourceDir(srcDir.toPath(), destDir.toPath(),
610-
findGeneratedSourceDir(destDir, sourceSet),
611-
Map.of("compiler", task.getName())));
615+
if (visitor.getRelativePath().getSegments().length == 1) {
616+
final File srcDir = visitor.getFile().getParentFile();
617+
sourceDirs.add(new DefaultSourceDir(srcDir.toPath(), destDir.toPath(),
618+
findGeneratedSourceDir(destDir, sourceSet),
619+
Map.of("compiler", task.getName())));
612620
}
613621
});
614622
}
@@ -620,9 +628,6 @@ private static Path findGeneratedSourceDir(File destDir, SourceSet sourceSet) {
620628
}
621629
String language = destDir.getParentFile().getName(); // java
622630
String sourceSetName = destDir.getName(); // main
623-
if (language == null) {
624-
return null;
625-
}
626631
// find the corresponding generated sources, same pattern, but under build/generated/sources/annotationProcessor/java/main
627632
for (File generatedDir : sourceSet.getOutput().getGeneratedSourcesDirs().getFiles()) {
628633
if (generatedDir.getParentFile() == null) {
@@ -636,6 +641,37 @@ private static Path findGeneratedSourceDir(File destDir, SourceSet sourceSet) {
636641
return null;
637642
}
638643

644+
/**
645+
* This method is meant to figure out the output directory containing class files for a compile task
646+
* which is not available in the plugin classpath. An example would be KotlinCompile.
647+
*
648+
* @param compileTask a compile task
649+
*/
650+
private static Path getClassesOutputDir(Task compileTask) {
651+
if (compileTask.getOutputs().getHasOutput()) {
652+
final AtomicReference<Path> result = new AtomicReference<>();
653+
compileTask.getOutputs().getFiles().getAsFileTree().visit(visitor -> {
654+
// We are looking for the first class file, since a compile task would typically
655+
// have a single output location for classes.
656+
// There in fact could be a few output locations, the rest though would typically be some internal caching bits
657+
if (visitor.getName().endsWith(".class")) {
658+
visitor.stopVisiting();
659+
var file = visitor.getFile();
660+
int relativeSegments = visitor.getRelativePath().getSegments().length;
661+
while (file != null && relativeSegments > 0) {
662+
relativeSegments--;
663+
file = file.getParentFile();
664+
}
665+
if (file != null) {
666+
result.set(file.toPath());
667+
}
668+
}
669+
});
670+
return result.get();
671+
}
672+
return null;
673+
}
674+
639675
private void addSubstitutedProject(PathList.Builder paths, File projectFile) {
640676
File mainResourceDirectory = new File(projectFile, MAIN_RESOURCES_OUTPUT);
641677
if (mainResourceDirectory.exists()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
plugins {
2+
kotlin("jvm")
3+
kotlin("plugin.allopen")
4+
id("io.quarkus")
5+
}
6+
7+
repositories {
8+
mavenLocal {
9+
content {
10+
includeGroupByRegex("io.quarkus.*")
11+
includeGroup("org.hibernate.orm")
12+
}
13+
}
14+
mavenCentral()
15+
}
16+
17+
val quarkusPlatformGroupId: String by project
18+
val quarkusPlatformArtifactId: String by project
19+
val quarkusPlatformVersion: String by project
20+
21+
dependencies {
22+
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
23+
implementation("io.quarkus:quarkus-kotlin")
24+
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
25+
implementation("io.quarkus:quarkus-arc")
26+
implementation("io.quarkus:quarkus-rest")
27+
testImplementation("io.quarkus:quarkus-junit5")
28+
testImplementation("io.rest-assured:rest-assured")
29+
}
30+
31+
group = "org.acme"
32+
version = "1.0-SNAPSHOT"
33+
34+
tasks.withType<Test> {
35+
systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager")
36+
}
37+
allOpen {
38+
annotation("jakarta.ws.rs.Path")
39+
annotation("jakarta.enterprise.context.ApplicationScoped")
40+
annotation("jakarta.persistence.Entity")
41+
annotation("io.quarkus.test.junit.QuarkusTest")
42+
}
43+
44+
kotlin {
45+
compilerOptions {
46+
javaParameters = true
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#Quarkus Gradle TS
2+
#Tue May 06 21:17:59 CEST 2025
3+
kotlinVersion=${kotlin.version}
4+
quarkusPluginVersion=${project.version}
5+
quarkusPlatformArtifactId=quarkus-bom
6+
quarkusPluginId=io.quarkus
7+
quarkusPlatformGroupId=io.quarkus
8+
quarkusPlatformVersion=${project.version}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pluginManagement {
2+
val quarkusPluginVersion: String by settings
3+
val quarkusPluginId: String by settings
4+
val kotlinVersion: String by settings
5+
repositories {
6+
mavenLocal {
7+
content {
8+
includeGroupByRegex("io.quarkus.*")
9+
includeGroup("org.hibernate.orm")
10+
}
11+
}
12+
mavenCentral()
13+
gradlePluginPortal()
14+
}
15+
plugins {
16+
id(quarkusPluginId) version quarkusPluginVersion
17+
kotlin("jvm") version kotlinVersion
18+
kotlin("plugin.allopen") version kotlinVersion
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.example
2+
3+
import jakarta.enterprise.context.ApplicationScoped
4+
5+
@ApplicationScoped
6+
class SomeBean {
7+
8+
fun someMethod() = "Hello from SomeBean!"
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package org.example
2+
3+
data class SomeDataClass(val someField: String)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
5+
bean-discovery-mode="annotated">
6+
</beans>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
plugins {
2+
kotlin("jvm")
3+
}
4+
5+
repositories {
6+
mavenLocal {
7+
content {
8+
includeGroupByRegex("io.quarkus.*")
9+
includeGroup("org.hibernate.orm")
10+
}
11+
}
12+
mavenCentral()
13+
}
14+
15+
group = "org.acme"
16+
version = "1.0-SNAPSHOT"
17+
18+
kotlin {
19+
compilerOptions {
20+
javaParameters = true
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#Quarkus Gradle TS
2+
#Tue May 06 21:43:16 CEST 2025
3+
kotlinVersion=${kotlin.version}
4+
quarkusPluginVersion=${project.version}
5+
quarkusPlatformArtifactId=quarkus-bom
6+
quarkusPluginId=io.quarkus
7+
quarkusPlatformGroupId=io.quarkus
8+
quarkusPlatformVersion=${project.version}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
pluginManagement {
2+
val quarkusPluginVersion: String by settings
3+
val quarkusPluginId: String by settings
4+
val kotlinVersion: String by settings
5+
repositories {
6+
mavenLocal {
7+
content {
8+
includeGroupByRegex("io.quarkus.*")
9+
includeGroup("org.hibernate.orm")
10+
}
11+
}
12+
mavenCentral()
13+
gradlePluginPortal()
14+
}
15+
plugins {
16+
id(quarkusPluginId) version quarkusPluginVersion
17+
kotlin("jvm") version kotlinVersion
18+
kotlin("plugin.allopen") version kotlinVersion
19+
}
20+
}
21+
22+
rootProject.name = "acme-subproject-nested"
23+
24+
include("acme-nested")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
plugins {
2+
kotlin("jvm")
3+
kotlin("plugin.allopen")
4+
id("io.quarkus")
5+
}
6+
7+
repositories {
8+
mavenLocal {
9+
content {
10+
includeGroupByRegex("io.quarkus.*")
11+
includeGroup("org.hibernate.orm")
12+
}
13+
}
14+
mavenCentral()
15+
}
16+
17+
val quarkusPlatformGroupId: String by project
18+
val quarkusPlatformArtifactId: String by project
19+
val quarkusPlatformVersion: String by project
20+
21+
dependencies {
22+
implementation("org.acme:acme-nested")
23+
24+
25+
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
26+
implementation("io.quarkus:quarkus-kotlin")
27+
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
28+
implementation("io.quarkus:quarkus-arc")
29+
implementation("io.quarkus:quarkus-rest")
30+
testImplementation("io.quarkus:quarkus-junit5")
31+
testImplementation("io.rest-assured:rest-assured")
32+
}
33+
34+
group = "org.acme"
35+
version = "1.0-SNAPSHOT"
36+
37+
tasks.withType<Test> {
38+
systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager")
39+
}
40+
allOpen {
41+
annotation("jakarta.ws.rs.Path")
42+
annotation("jakarta.enterprise.context.ApplicationScoped")
43+
annotation("jakarta.persistence.Entity")
44+
annotation("io.quarkus.test.junit.QuarkusTest")
45+
}
46+
47+
kotlin {
48+
compilerOptions {
49+
javaParameters = true
50+
}
51+
}

0 commit comments

Comments
 (0)