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

feature: Add dependencyModules request support #536

Merged
merged 3 commits into from Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -7,6 +7,11 @@ import ch.epfl.scala.bsp4j.CompileProvider
import ch.epfl.scala.bsp4j.CppOptionsItem
import ch.epfl.scala.bsp4j.CppOptionsParams
import ch.epfl.scala.bsp4j.CppOptionsResult
import ch.epfl.scala.bsp4j.DependencyModule
import ch.epfl.scala.bsp4j.DependencyModuleDataKind
import ch.epfl.scala.bsp4j.DependencyModulesItem
import ch.epfl.scala.bsp4j.DependencyModulesParams
import ch.epfl.scala.bsp4j.DependencyModulesResult
import ch.epfl.scala.bsp4j.DependencySourcesItem
import ch.epfl.scala.bsp4j.DependencySourcesParams
import ch.epfl.scala.bsp4j.DependencySourcesResult
Expand Down Expand Up @@ -447,6 +452,26 @@ class BspProjectMapper(
return ScalaMainClassesResult(items)
}

fun buildDependencyModules(project: Project, params: DependencyModulesParams): DependencyModulesResult {
val targetSet = params.targets.toSet()
val dependencyModulesItems = project.modules.filter { targetSet.contains(BuildTargetIdentifier(it.label.value)) }.map { module ->
val buildTargetId = BuildTargetIdentifier(module.label.value)
val moduleItems = DependencyMapper.allModuleDependencies(project, module).flatMap { libraryDep ->
if (libraryDep.outputs.isNotEmpty()) {
val mavenDependencyModule = DependencyMapper.extractMavenDependencyInfo(libraryDep)
val dependencyModule = DependencyModule(libraryDep.label, mavenDependencyModule?.version ?: "")
if (mavenDependencyModule != null) {
dependencyModule.data = mavenDependencyModule
dependencyModule.dataKind = DependencyModuleDataKind.MAVEN
}
listOf(dependencyModule)
} else emptyList()
}
DependencyModulesItem(buildTargetId, moduleItems)
}
return DependencyModulesResult(dependencyModulesItems)
}

fun rustWorkspace(
project: Project,
params: RustWorkspaceParams
Expand Down
@@ -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
}
}
Expand Up @@ -160,8 +160,8 @@ class ProjectSyncService(private val bspMapper: BspProjectMapper, private val pr
cancelChecker: CancelChecker,
params: DependencyModulesParams
): DependencyModulesResult {
// TODO https://youtrack.jetbrains.com/issue/BAZEL-616
return DependencyModulesResult(emptyList())
val project = projectProvider.get(cancelChecker)
return bspMapper.buildDependencyModules(project, params)
}

fun rustWorkspace(
Expand Down
Expand Up @@ -18,3 +18,12 @@ kt_test(
"//server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync",
],
)

kt_test(
name = "DependencyMapperTest",
size = "small",
src = "DependencyMapperTest.kt",
deps = [
"//server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync",
],
)
@@ -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")
tgodzik marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}