Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: Add dependencyModules request support (#536)
* feature: Add dependencyModules request support This would be super useful to use as an alternative to libraries request, which is not standard BSP request. * chore: Add unit tests for dependency modules * bugfix: Make dependency modules work for bazelmod
- Loading branch information
Showing
5 changed files
with
221 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/DependencyMapper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package org.jetbrains.bsp.bazel.server.sync | ||
|
||
import ch.epfl.scala.bsp4j.MavenDependencyModule | ||
import ch.epfl.scala.bsp4j.MavenDependencyModuleArtifact | ||
import org.jetbrains.bsp.bazel.server.sync.model.Library | ||
import org.jetbrains.bsp.bazel.server.sync.model.Module | ||
import org.jetbrains.bsp.bazel.server.sync.model.Project | ||
import java.util.* | ||
|
||
object DependencyMapper { | ||
|
||
fun extractMavenDependencyInfo(lib: Library): MavenDependencyModule? { | ||
if (lib.outputs.isEmpty()) return null | ||
val jars = lib.outputs.map { uri -> uri.toString() }.map { | ||
MavenDependencyModuleArtifact(it) | ||
} | ||
val sourceJars = lib.sources.map { uri -> uri.toString() }.map { | ||
val artifact = MavenDependencyModuleArtifact(it) | ||
artifact.classifier = "sources" | ||
artifact | ||
} | ||
/* For example: | ||
* @@rules_jvm_external~override~maven~maven//:org_apache_commons_commons_lang3 | ||
* @maven//:org_scala_lang_scala_library | ||
**/ | ||
val orgStart = lib.label.split("//:").lastOrNull()?.split('_')?.firstOrNull() ?: "org" | ||
// Matches the Maven group (organization), artifact, and version in the Bazel dependency | ||
// string such as .../execroot/monorepo/bazel-out/k8-fastbuild/bin/external/maven/com/google/guava/guava/31.1-jre/processed_guava-31.1-jre.jar | ||
// bazel-out/k8-fastbuild/bin/external/rules_jvm_external~~maven~name/v1/https/repo1.maven.org/maven2/com/google/auto/service/auto-service-annotations/1.1.1/header_auto-service-annotations-1.1.1.jar | ||
val regexPattern = """.*/($orgStart/.+)/([^/]+)/([^/]+)/[^/]+.jar""".toRegex() | ||
val dependencyPath = lib.outputs.firstOrNull()?.toString() | ||
// Find matches in the dependency path | ||
if (dependencyPath != null) { | ||
val matchResult = regexPattern.find(dependencyPath) | ||
// If a match is found, group values are extracted; otherwise, null is returned | ||
return matchResult?.let { | ||
val (organization, artifact, version) = it.destructured | ||
MavenDependencyModule(organization.replace("/", "."), artifact, version, jars + sourceJars) | ||
} | ||
} else { | ||
return null | ||
} | ||
} | ||
|
||
|
||
fun allModuleDependencies(project: Project, module: Module): HashSet<Library> { | ||
val toResolve = LinkedList<String>() | ||
toResolve.addAll(module.directDependencies.map { it.value }) | ||
val accumulator = HashSet<Library>() | ||
while (toResolve.isNotEmpty()) { | ||
val lib = project.libraries[toResolve.pop()] | ||
if (lib != null && !accumulator.contains(lib)) { | ||
accumulator.add(lib) | ||
toResolve.addAll(lib.dependencies) | ||
} | ||
} | ||
return accumulator | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
server/src/test/kotlin/org/jetbrains/bsp/bazel/server/sync/DependencyMapperTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package org.jetbrains.bsp.bazel.server.sync | ||
|
||
import ch.epfl.scala.bsp4j.MavenDependencyModule | ||
import ch.epfl.scala.bsp4j.MavenDependencyModuleArtifact | ||
import io.kotest.matchers.shouldBe | ||
import org.jetbrains.bsp.bazel.bazelrunner.BazelRelease | ||
import org.jetbrains.bsp.bazel.server.sync.model.Label | ||
import org.jetbrains.bsp.bazel.server.sync.model.Library | ||
import org.jetbrains.bsp.bazel.server.sync.model.Module | ||
import org.jetbrains.bsp.bazel.server.sync.model.Project | ||
import org.jetbrains.bsp.bazel.server.sync.model.SourceSet | ||
import org.junit.jupiter.api.Test | ||
import java.net.URI | ||
import java.nio.file.Paths | ||
|
||
class DependencyMapperTest { | ||
|
||
private val cacheLocation = "file:///home/user/.cache/bazel/_bazel_user/ae7b7b315151086e31e3b97f9ddba009/execroot/monorepo/bazel-out/k8-fastbuild-ST-4a519fd6d3e4" | ||
|
||
@Test | ||
fun `should translate dependency`() { | ||
val jarUri = URI.create("$cacheLocation/bin/external/maven/org/scala-lang/scala-library/2.13.11/processed_scala-library-2.13.11.jar") | ||
val jarSourcesUri = URI.create("$cacheLocation/bin/external/maven/org/scala-lang/scala-library/2.13.11/scala-library-2.13.11-sources.jar") | ||
val lib1 = Library( | ||
"@maven//:org_scala_lang_scala_library", | ||
setOf(jarUri), | ||
setOf(jarSourcesUri), | ||
emptyList() | ||
) | ||
val expectedMavenArtifact = MavenDependencyModuleArtifact(jarUri.toString()) | ||
val expectedMavenSourcesArtifact = MavenDependencyModuleArtifact(jarSourcesUri.toString()) | ||
expectedMavenSourcesArtifact.classifier = "sources" | ||
val expectedDependency = MavenDependencyModule("org.scala-lang", "scala-library", "2.13.11", listOf( | ||
expectedMavenArtifact, | ||
expectedMavenSourcesArtifact | ||
)) | ||
val dependency = DependencyMapper.extractMavenDependencyInfo(lib1) | ||
|
||
dependency shouldBe expectedDependency | ||
} | ||
|
||
@Test | ||
fun `should bazelmod translate dependency`() { | ||
val jarUri = URI.create("$cacheLocation/bin/external/rules_jvm_external~~maven~name/v1/https/repo1.maven.org/maven2/com/google/auto/service/auto-service-annotations/1.1.1/header_auto-service-annotations-1.1.1.jar") | ||
val jarSourcesUri = URI.create("$cacheLocation/bin/external/rules_jvm_external~~maven~name/v1/https/repo1.maven.org/maven2/com/google/auto/service/auto-service-annotations/1.1.1/header_auto-service-annotations-1.1.1-sources.jar") | ||
val lib1 = Library( | ||
"@@rules_jvm_external~override~maven~maven//:com_google_auto_service_auto_service_annotations", | ||
setOf(jarUri), | ||
setOf(jarSourcesUri), | ||
emptyList() | ||
) | ||
val expectedMavenArtifact = MavenDependencyModuleArtifact(jarUri.toString()) | ||
val expectedMavenSourcesArtifact = MavenDependencyModuleArtifact(jarSourcesUri.toString()) | ||
expectedMavenSourcesArtifact.classifier = "sources" | ||
val expectedDependency = MavenDependencyModule("com.google.auto.service", "auto-service-annotations", "1.1.1", listOf( | ||
expectedMavenArtifact, | ||
expectedMavenSourcesArtifact | ||
)) | ||
val dependency = DependencyMapper.extractMavenDependencyInfo(lib1) | ||
|
||
dependency shouldBe expectedDependency | ||
} | ||
|
||
@Test | ||
fun `should not translate non maven dependency`() { | ||
val lib1 = Library( | ||
"@//projects/v1:scheduler", | ||
emptySet(), | ||
emptySet(), | ||
emptyList() | ||
) | ||
val dependency = DependencyMapper.extractMavenDependencyInfo(lib1) | ||
|
||
dependency shouldBe null | ||
} | ||
|
||
@Test | ||
fun `should gather deps transitively`() { | ||
val jarUri = URI.create("$cacheLocation/bin/external/maven/org/scala-lang/scala-library/2.13.11/processed_scala-library-2.13.11.jar") | ||
val jarSourcesUri = URI.create("$cacheLocation/bin/external/maven/org/scala-lang/scala-library/2.13.11/scala-library-2.13.11-sources.jar") | ||
val lib1 = Library( | ||
"@maven//:org_scala_lang_scala_library", | ||
setOf(jarUri), | ||
setOf(jarSourcesUri), | ||
emptyList() | ||
) | ||
val lib2 = Library( | ||
"@maven//:org_scala_lang_scala_library2", | ||
emptySet(), | ||
emptySet(), | ||
listOf(lib1.label) | ||
) | ||
val lib3 = Library( | ||
"@maven//:org_scala_lang_scala_library3", | ||
emptySet(), | ||
emptySet(), | ||
listOf(lib1.label, lib2.label) | ||
) | ||
val lib4 = Library( | ||
"@maven//:org_scala_lang_scala_library4", | ||
emptySet(), | ||
emptySet(), | ||
listOf(lib3.label, lib2.label) | ||
) | ||
val libraries = mapOf(lib1.label to lib1, lib2.label to lib2, lib3.label to lib3, lib4.label to lib4) | ||
val currentUri = Paths.get(".").toUri() | ||
val project = Project(currentUri, emptyList(), emptyMap(), libraries, emptyList(), BazelRelease(6)) | ||
val module = Module( | ||
Label(""), | ||
true, | ||
listOf(Label(lib4.label)), | ||
emptySet(), | ||
emptySet(), | ||
currentUri, | ||
SourceSet(emptySet(), emptySet()), | ||
emptySet(), | ||
emptySet(), | ||
emptySet(), | ||
null, | ||
emptyMap() | ||
) | ||
val foundLibraries = DependencyMapper.allModuleDependencies(project, module) | ||
|
||
foundLibraries shouldBe setOf(lib1, lib2, lib3, lib4) | ||
} | ||
} |