Skip to content

Commit ba33ea2

Browse files
committed
Fix included Kotlin build in quarkusDev
1 parent a721231 commit ba33ea2

File tree

23 files changed

+770
-34
lines changed

23 files changed

+770
-34
lines changed

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

Lines changed: 71 additions & 34 deletions
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,58 @@ 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+
581+
// if the above failed, there could still be a KotlinCompile task that's not easily discoverable
582+
if (originalSourceDirsSize == sourceDirs.size()) {
583+
final Path outputDir = getClassesOutputDir(task);
584+
if (outputDir != null && task.getInputs().getHasInputs()) {
585+
task.getInputs().getSourceFiles().getAsFileTree().visit(visitor -> {
586+
if (visitor.getRelativePath().getSegments().length == 1) {
587+
sourceDirs.add(SourceDir.of(visitor.getFile().getParentFile().toPath(), outputDir));
588+
}
589+
});
590+
}
591+
break;
592+
}
593+
}
579594
}
580595
}
581596

582597
private static void doConfigureKotlinJvmCompile(Project project, FileCollection allClassesDirs,
583598
List<SourceDir> sourceDirs, SourceSet sourceSet) {
584599
// Use KotlinJvmCompile.class in a separate method to prevent that maybeConfigureKotlinJvmCompile() runs into
585600
// a ClassNotFoundException due to actually using KotlinJvmCompile.class.
586-
project.getTasks().withType(KotlinJvmCompile.class, t -> configureCompileTask(t.getSources().getAsFileTree(),
601+
project.getTasks().withType(KotlinCompileTool.class, t -> configureCompileTask(t.getSources().getAsFileTree(),
587602
t.getDestinationDirectory(), allClassesDirs, sourceDirs, t, sourceSet));
588603
}
589604

590605
private static void configureCompileTask(FileTree sources, DirectoryProperty destinationDirectory,
591606
FileCollection allClassesDirs, List<SourceDir> sourceDirs, Task task, SourceSet sourceSet) {
592-
if (!task.getEnabled()) {
593-
return;
594-
}
595-
if (sources.isEmpty()) {
607+
if (!task.getEnabled() || sources.isEmpty()) {
596608
return;
597609
}
598-
599610
final File destDir = destinationDirectory.getAsFile().get();
600611
if (!allClassesDirs.contains(destDir)) {
601612
return;
602613
}
603-
sources.visit(a -> {
614+
sources.visit(visitor -> {
604615
// 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())));
616+
if (visitor.getRelativePath().getSegments().length == 1) {
617+
final File srcDir = visitor.getFile().getParentFile();
618+
sourceDirs.add(new DefaultSourceDir(srcDir.toPath(), destDir.toPath(),
619+
findGeneratedSourceDir(destDir, sourceSet),
620+
Map.of("compiler", task.getName())));
612621
}
613622
});
614623
}
@@ -620,9 +629,6 @@ private static Path findGeneratedSourceDir(File destDir, SourceSet sourceSet) {
620629
}
621630
String language = destDir.getParentFile().getName(); // java
622631
String sourceSetName = destDir.getName(); // main
623-
if (language == null) {
624-
return null;
625-
}
626632
// find the corresponding generated sources, same pattern, but under build/generated/sources/annotationProcessor/java/main
627633
for (File generatedDir : sourceSet.getOutput().getGeneratedSourcesDirs().getFiles()) {
628634
if (generatedDir.getParentFile() == null) {
@@ -636,6 +642,37 @@ private static Path findGeneratedSourceDir(File destDir, SourceSet sourceSet) {
636642
return null;
637643
}
638644

645+
/**
646+
* This method is meant to figure out the output directory containing class files for a compile task
647+
* which is not available in the plugin classpath. An example would be KotlinCompile.
648+
*
649+
* @param compileTask a compile task
650+
*/
651+
private static Path getClassesOutputDir(Task compileTask) {
652+
if (compileTask.getOutputs().getHasOutput()) {
653+
final AtomicReference<Path> result = new AtomicReference<>();
654+
compileTask.getOutputs().getFiles().getAsFileTree().visit(visitor -> {
655+
// We are looking for the first class file, since a compile task would typically
656+
// have a single output location for classes.
657+
// There in fact could be a few output locations, the rest though would typically be some internal caching bits
658+
if (visitor.getName().endsWith(".class")) {
659+
visitor.stopVisiting();
660+
var file = visitor.getFile();
661+
int relativeSegments = visitor.getRelativePath().getSegments().length;
662+
while (file != null && relativeSegments > 0) {
663+
relativeSegments--;
664+
file = file.getParentFile();
665+
}
666+
if (file != null) {
667+
result.set(file.toPath());
668+
}
669+
}
670+
});
671+
return result.get();
672+
}
673+
return null;
674+
}
675+
639676
private void addSubstitutedProject(PathList.Builder paths, File projectFile) {
640677
File mainResourceDirectory = new File(projectFile, MAIN_RESOURCES_OUTPUT);
641678
if (mainResourceDirectory.exists()) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
plugins {
2+
kotlin("jvm") version "2.0.21"
3+
kotlin("plugin.allopen") version "2.0.21"
4+
id("io.quarkus")
5+
}
6+
7+
repositories {
8+
mavenCentral()
9+
mavenLocal()
10+
}
11+
12+
val quarkusPlatformGroupId: String by project
13+
val quarkusPlatformArtifactId: String by project
14+
val quarkusPlatformVersion: String by project
15+
16+
dependencies {
17+
implementation("org.example:quarkus-nested")
18+
19+
20+
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
21+
implementation("io.quarkus:quarkus-kotlin")
22+
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
23+
implementation("io.quarkus:quarkus-arc")
24+
implementation("io.quarkus:quarkus-rest")
25+
testImplementation("io.quarkus:quarkus-junit5")
26+
testImplementation("io.rest-assured:rest-assured")
27+
}
28+
29+
group = "org.example"
30+
version = "1.0-SNAPSHOT"
31+
32+
java {
33+
sourceCompatibility = JavaVersion.VERSION_21
34+
targetCompatibility = JavaVersion.VERSION_21
35+
}
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+
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21
50+
javaParameters = true
51+
}
52+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Gradle properties
2+
quarkusPluginId=io.quarkus
3+
quarkusPluginVersion=999-SNAPSHOT
4+
quarkusPlatformGroupId=io.quarkus
5+
quarkusPlatformArtifactId=quarkus-bom
6+
quarkusPlatformVersion=999-SNAPSHOT
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
plugins {
2+
kotlin("jvm") version "2.0.21"
3+
}
4+
5+
repositories {
6+
mavenCentral()
7+
mavenLocal()
8+
}
9+
10+
group = "org.example"
11+
version = "1.0-SNAPSHOT"
12+
13+
java {
14+
sourceCompatibility = JavaVersion.VERSION_21
15+
targetCompatibility = JavaVersion.VERSION_21
16+
}
17+
18+
kotlin {
19+
compilerOptions {
20+
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21
21+
javaParameters = true
22+
}
23+
}

integration-tests/gradle/src/main/resources/included-kotlin-build/quarkus-subproject-nested/gradle.properties

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
plugins {
2+
kotlin("jvm") version "2.0.21"
3+
kotlin("plugin.allopen") version "2.0.21"
4+
id("io.quarkus") version "3.21.3"
5+
}
6+
7+
repositories {
8+
mavenCentral()
9+
mavenLocal()
10+
}
11+
12+
val quarkusPlatformGroupId: String by project
13+
val quarkusPlatformArtifactId: String by project
14+
val quarkusPlatformVersion: String by project
15+
16+
dependencies {
17+
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
18+
implementation("io.quarkus:quarkus-kotlin")
19+
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
20+
implementation("io.quarkus:quarkus-arc")
21+
implementation("io.quarkus:quarkus-rest")
22+
testImplementation("io.quarkus:quarkus-junit5")
23+
testImplementation("io.rest-assured:rest-assured")
24+
}
25+
26+
group = "org.example"
27+
version = "1.0-SNAPSHOT"
28+
29+
java {
30+
sourceCompatibility = JavaVersion.VERSION_21
31+
targetCompatibility = JavaVersion.VERSION_21
32+
}
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+
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21
47+
javaParameters = true
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Gradle properties
2+
quarkusPluginId=io.quarkus
3+
quarkusPluginVersion=999-SNAPSHOT
4+
quarkusPlatformGroupId=io.quarkus
5+
quarkusPlatformArtifactId=quarkus-bom
6+
quarkusPlatformVersion=999-SNAPSHOT

0 commit comments

Comments
 (0)