Skip to content

Commit 19e7fb3

Browse files
committed
feat: gradle multi module support
1 parent 269d8fb commit 19e7fb3

File tree

3 files changed

+82
-9
lines changed

3 files changed

+82
-9
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ Right now, this language server is at its infancy and thus not ready to use for
2020
- ✅ Hover: fully working
2121
- 🚧 Go to definition: working except for kotlin binary dependencies (considering using the background index for this if we cannot make it work), it would also be nice to use a decompiler to jump into .class files (the analysis api provides `KotlinClassFileDecompiler` for kotlin .class files, fernflower may be used for java .class files)
2222
- 🚧 Build system integration: there is support for
23-
* Single module Gradle projects
23+
* Gradle projects (single and multi module)
2424
* Single module Android projects (uses debug variant and does not handle source set merging yet)
2525
* Needs work on:
26-
* Multi module Gradle projects
26+
* Multimodule Android projects
2727
* KMP projects (targeting JVM, native target needs investigation on how to do it)
2828

2929
## Installing

app/src/main/kotlin/org/kotlinlsp/buildsystem/Gradle.kt

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import org.eclipse.lsp4j.WorkDoneProgressKind
77
import org.gradle.tooling.GradleConnector
88
import org.gradle.tooling.events.OperationType
99
import org.gradle.tooling.model.GradleModuleVersion
10+
import org.gradle.tooling.model.idea.IdeaModuleDependency
1011
import org.gradle.tooling.model.idea.IdeaProject
1112
import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency
1213
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreApplicationEnvironment
1314
import org.jetbrains.kotlin.config.LanguageVersion
15+
import org.jetbrains.kotlin.util.collectionUtils.concat
1416
import org.kotlinlsp.analysis.ProgressNotifier
1517
import org.kotlinlsp.analysis.modules.*
1618
import org.kotlinlsp.common.getCachePath
@@ -97,6 +99,10 @@ class GradleBuildSystem(
9799
.filter { it.scope.scope != "RUNTIME" } // We don't need runtime deps for a LSP
98100
.partition { it.scope.scope == "TEST" }
99101

102+
val ideaSourceModuleDeps = module
103+
.dependencies
104+
.filterIsInstance<IdeaModuleDependency>()
105+
100106
// Register regular dependencies
101107
(ideaTestDeps.asSequence() + ideaSourceDeps.asSequence()).forEach {
102108
val id = it.gradleModuleVersion.formatted()
@@ -131,27 +137,42 @@ class GradleBuildSystem(
131137
val isAndroidModule = ideaExtraSourceDeps.isNotEmpty() // TODO Find a better way to know this
132138
val sourceModuleId = module.name
133139
val sourceDirs = ideaSourceDirs.map { it.directory.absolutePath }
134-
val sourceDeps = ideaSourceDeps.map { it.gradleModuleVersion.formatted() } + ideaExtraSourceDeps.map {
135-
it.directory.toString().removePrefix("jar:")
136-
} + if(!isAndroidModule) { listOf("JDK") } else { emptyList() }
140+
val sourceDeps =
141+
ideaSourceDeps.asSequence().map { it.gradleModuleVersion.formatted() }
142+
.plus(
143+
ideaExtraSourceDeps.map {
144+
it.directory.toString().removePrefix("jar:")
145+
}
146+
).plus(
147+
if (!isAndroidModule) {
148+
sequenceOf("JDK")
149+
} else {
150+
emptySequence()
151+
}
152+
).plus(
153+
ideaSourceModuleDeps.map { it.targetModuleName }
154+
)
137155
modules[sourceModuleId] = SerializedModule(
138156
id = sourceModuleId,
139157
isSource = true,
140-
dependencies = sourceDeps,
158+
dependencies = sourceDeps.toList(),
141159
contentRoots = sourceDirs,
142160
kotlinVersion = LanguageVersion.KOTLIN_2_1.versionString, // TODO Figure out this
143161
javaVersion = ideaProject.jdkName
144162
)
145163

146164
// Register test module
147-
if(contentRoot.testDirectories.isNotEmpty()) {
165+
if (contentRoot.testDirectories.isNotEmpty()) {
148166
val testModuleId = "${sourceModuleId}-test"
149167
val testDirs = contentRoot.testDirectories.map { it.directory.absolutePath }
150-
val testDeps = sourceDeps + ideaTestDeps.map { it.gradleModuleVersion.formatted() } + listOf(sourceModuleId)
168+
val testDeps =
169+
sourceDeps
170+
.plus(ideaTestDeps.map { it.gradleModuleVersion.formatted() })
171+
.plus(sequenceOf(sourceModuleId))
151172
modules[testModuleId] = SerializedModule(
152173
id = testModuleId,
153174
isSource = true,
154-
dependencies = testDeps,
175+
dependencies = testDeps.toList(),
155176
contentRoots = testDirs,
156177
kotlinVersion = LanguageVersion.KOTLIN_2_1.versionString, // TODO Figure out this
157178
javaVersion = ideaProject.jdkName

app/src/test/kotlin/org/kotlinlsp/Gradle.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,58 @@ class Gradle {
7878
assertEquals(modules[1].dependencies.filter { it.isSourceModule }.size, 1)
7979
}
8080

81+
@Test
82+
fun `loads multi module project successfully`() = scenario("multi-module") { buildSystem ->
83+
// Act
84+
val (modules, _) = buildSystem.resolveModulesIfNeeded(cachedMetadata = null)!!
85+
86+
// Assert
87+
assertEquals(modules.size, 4)
88+
assertEquals(modules[0].isSourceModule, true)
89+
assertEquals(
90+
modules[0].contentRoots,
91+
listOf(
92+
Path("$cwd/test-projects/multi-module/app/src/main/java"),
93+
Path("$cwd/test-projects/multi-module/app/src/main/kotlin"),
94+
)
95+
)
96+
assertEquals(modules[0].dependencies.size, 4)
97+
val depNames = modules[0].dependencies.map { it.id }.toSet()
98+
assertTrue("submodule" in depNames)
99+
100+
assertEquals(modules[1].isSourceModule, true)
101+
assertEquals(
102+
modules[1].contentRoots,
103+
listOf(
104+
Path("$cwd/test-projects/multi-module/app/src/test/java"),
105+
Path("$cwd/test-projects/multi-module/app/src/test/kotlin"),
106+
)
107+
)
108+
assertEquals(modules[1].dependencies.size, 5)
109+
assertEquals(modules[1].dependencies.filter { it.isSourceModule }.size, 2)
110+
111+
assertEquals(modules[2].isSourceModule, true)
112+
assertEquals(
113+
modules[2].contentRoots,
114+
listOf(
115+
Path("$cwd/test-projects/multi-module/submodule/src/main/java"),
116+
Path("$cwd/test-projects/multi-module/submodule/src/main/kotlin"),
117+
)
118+
)
119+
assertEquals(modules[2].dependencies.size, 3)
120+
121+
assertEquals(modules[3].isSourceModule, true)
122+
assertEquals(
123+
modules[3].contentRoots,
124+
listOf(
125+
Path("$cwd/test-projects/multi-module/submodule/src/test/java"),
126+
Path("$cwd/test-projects/multi-module/submodule/src/test/kotlin"),
127+
)
128+
)
129+
assertEquals(modules[3].dependencies.size, 4)
130+
assertEquals(modules[3].dependencies.filter { it.isSourceModule }.size, 1)
131+
}
132+
81133
@Test
82134
fun `loads android project successfully`() = scenario("android") { buildSystem ->
83135
// Act

0 commit comments

Comments
 (0)