From 47cdfa8b472f1fb111c86965360ad990d6c4670f Mon Sep 17 00:00:00 2001 From: Xuan-Son Trinh Date: Tue, 31 Oct 2023 09:17:09 +0000 Subject: [PATCH] [feature] support bzlmod in rules detection | #BAZEL-700 Done - detect bzlmod - support dynamic rules detection when on bzlmod Merge-request: BAZEL-MR-578 Merged-by: Xuan Son Trinh --- WORKSPACE | 2 + .../kt/{kt_info.bzl => kt_info.bzl.template} | 6 +- .../bsp/bazel/bazelrunner/BazelInfo.kt | 21 +- .../bazel/bazelrunner/BazelInfoResolver.kt | 38 ++-- .../bsp/bazel/bazelrunner/BazelRunner.kt | 6 +- .../bazelrunner/BazelRunnerBuildBuilder.kt | 2 +- .../bazel/bazelrunner/BazelRunnerBuilder.kt | 2 +- .../bazelrunner/BazelRunnerCommandBuilder.kt | 24 ++- .../bsp/bazel/bazelrunner/BazelReleaseTest.kt | 4 +- .../bazel/bazelrunner/StoredBazelInfoTest.kt | 10 +- .../bazel/install/EnvironmentCreatorTest.kt | 2 +- .../bsp/bazel/server/bsp/managers/BUILD | 2 + .../managers/BazelBspEnvironmentManager.kt | 186 +++++++++++------- .../bsp/managers/BazelExternalRulesQuery.kt | 122 +++++++++--- .../bazel/server/common/ServerContainer.kt | 129 ++++++------ .../bazel/server/sync/BazelPathsResolver.kt | 2 +- .../bazel/server/sync/BazelProjectMapper.kt | 4 +- .../bsp/bazel/server/sync/ProjectResolver.kt | 120 +++++------ .../bsp/bazel/server/bsp/managers/BUILD | 1 + .../BazelBspEnvironmentManagerTest.kt | 22 ++- .../languages/LanguagePluginServiceTest.kt | 1 + .../java/IdeClasspathResolverTest.kt | 9 +- 22 files changed, 437 insertions(+), 278 deletions(-) rename aspects/rules/kt/{kt_info.bzl => kt_info.bzl.template} (88%) diff --git a/WORKSPACE b/WORKSPACE index 529d9f709..67f562606 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -215,7 +215,9 @@ maven_install( "commons-cli:commons-cli:jar:1.6.0", "org.apache.logging.log4j:log4j-api:2.21.1", "org.apache.logging.log4j:log4j-core:2.21.1", + "org.apache.velocity:velocity-engine-core:2.3", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3", + "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0", "org.junit.jupiter:junit-jupiter:5.10.0", "com.fasterxml.jackson.core:jackson-databind:2.15.3", "com.fasterxml.jackson.module:jackson-module-kotlin:2.15.3", diff --git a/aspects/rules/kt/kt_info.bzl b/aspects/rules/kt/kt_info.bzl.template similarity index 88% rename from aspects/rules/kt/kt_info.bzl rename to aspects/rules/kt/kt_info.bzl.template index 7a22a8197..8efa4e030 100644 --- a/aspects/rules/kt/kt_info.bzl +++ b/aspects/rules/kt/kt_info.bzl.template @@ -1,8 +1,8 @@ -load("@io_bazel_rules_kotlin//kotlin/internal:defs.bzl", "KtJvmInfo") -load("@io_bazel_rules_kotlin//kotlin/internal:opts.bzl", "KotlincOptions") +load("@${ruleName}//kotlin/internal:defs.bzl", "KtJvmInfo") +load("@${ruleName}//kotlin/internal:opts.bzl", "KotlincOptions") load("//aspects:utils/utils.bzl", "convert_struct_to_dict", "create_proto", "create_struct") -KOTLIN_TOOLCHAIN_TYPE = "@io_bazel_rules_kotlin//kotlin/internal:kt_toolchain_type" +KOTLIN_TOOLCHAIN_TYPE = "@${ruleName}//kotlin/internal:kt_toolchain_type" def extract_kotlin_info(target, ctx, **kwargs): if KtJvmInfo not in target: diff --git a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelInfo.kt b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelInfo.kt index 9da203b9d..676d55243 100644 --- a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelInfo.kt +++ b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelInfo.kt @@ -9,16 +9,19 @@ interface BazelInfo { val outputBase: Path val workspaceRoot: Path val release: BazelRelease + val isBzlModEnabled: Boolean } data class BazelRelease( val major: Int ) { - fun mainRepositoryReferencePrefix() = when(major) { + fun mainRepositoryReferencePrefix(isBzlModEnabled: Boolean) = when (major) { in 0..3 -> throw RuntimeException("Unsupported Bazel version, use Bazel 4 or newer") in 4..5 -> "//" - else -> "@//" + else -> + if (isBzlModEnabled) "@@//" + else "@//" } companion object { @@ -44,16 +47,17 @@ data class BazelRelease( } } + fun BazelRelease?.orLatestSupported() = this ?: BazelRelease.LATEST_SUPPORTED_MAJOR data class BasicBazelInfo( - override val execRoot: String, - override val outputBase: Path, - override val workspaceRoot: Path, - override val release: BazelRelease + override val execRoot: String, + override val outputBase: Path, + override val workspaceRoot: Path, + override val release: BazelRelease, + override val isBzlModEnabled: Boolean, ) : BazelInfo - class LazyBazelInfo(bazelInfoSupplier: () -> BazelInfo) : BazelInfo { private val bazelInfo: BazelInfo by lazy { bazelInfoSupplier() } @@ -68,4 +72,7 @@ class LazyBazelInfo(bazelInfoSupplier: () -> BazelInfo) : BazelInfo { override val release: BazelRelease get() = bazelInfo.release + + override val isBzlModEnabled: Boolean + get() = bazelInfo.isBzlModEnabled } diff --git a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoResolver.kt b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoResolver.kt index 41a83a681..9b80b2254 100644 --- a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoResolver.kt +++ b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoResolver.kt @@ -1,12 +1,13 @@ package org.jetbrains.bsp.bazel.bazelrunner +import ch.epfl.scala.bsp4j.StatusCode import org.eclipse.lsp4j.jsonrpc.CancelChecker import org.jetbrains.bsp.bazel.commons.escapeNewLines import java.nio.file.Paths class BazelInfoResolver( - private val bazelRunner: BazelRunner, - private val storage: BazelInfoStorage + private val bazelRunner: BazelRunner, + private val storage: BazelInfoStorage ) { fun resolveBazelInfo(cancelChecker: CancelChecker): BazelInfo { @@ -14,37 +15,44 @@ class BazelInfoResolver( } private fun bazelInfoFromBazel(cancelChecker: CancelChecker): BazelInfo { + val isBzlModEnabled = calculateBzlModEnabled(cancelChecker) val processResult = bazelRunner.commandBuilder() .info().executeBazelCommand(useBuildFlags = false) .waitAndGetResult(cancelChecker,true) - return parseBazelInfo(processResult).also { storage.store(it) } + return parseBazelInfo(processResult, isBzlModEnabled).also { storage.store(it) } } - private fun parseBazelInfo(bazelProcessResult: BazelProcessResult): BasicBazelInfo { + private fun parseBazelInfo(bazelProcessResult: BazelProcessResult, isBzlModEnabled: Boolean): BasicBazelInfo { val outputMap = bazelProcessResult - .stdoutLines - .mapNotNull { line -> - InfoLinePattern.matchEntire(line)?.let { it.groupValues[1] to it.groupValues[2] } - }.toMap() + .stdoutLines + .mapNotNull { line -> + InfoLinePattern.matchEntire(line)?.let { it.groupValues[1] to it.groupValues[2] } + }.toMap() fun BazelProcessResult.meaningfulOutput() = if (isNotSuccess) stderr else stdout fun extract(name: String): String = - outputMap[name] - ?: error("Failed to resolve $name from bazel info in ${bazelRunner.workspaceRoot}. " + - "Bazel Info output: '${bazelProcessResult.meaningfulOutput().escapeNewLines()}'") + outputMap[name] + ?: error("Failed to resolve $name from bazel info in ${bazelRunner.workspaceRoot}. " + + "Bazel Info output: '${bazelProcessResult.meaningfulOutput().escapeNewLines()}'") fun obtainBazelReleaseVersion() = BazelRelease.fromReleaseString(extract("release")) ?: bazelRunner.workspaceRoot?.let { BazelRelease.fromBazelVersionFile(it) }.orLatestSupported() return BasicBazelInfo( - execRoot = extract("execution_root"), - outputBase = Paths.get(extract("output_base")), - workspaceRoot = Paths.get(extract("workspace")), - release = obtainBazelReleaseVersion() + execRoot = extract("execution_root"), + outputBase = Paths.get(extract("output_base")), + workspaceRoot = Paths.get(extract("workspace")), + release = obtainBazelReleaseVersion(), + isBzlModEnabled = isBzlModEnabled ) } + // this method does a small check whether bzlmod is enabled in the project + // by running an arbitrary a bazel mod command and check for ok status code + private fun calculateBzlModEnabled(cancelChecker: CancelChecker) = + bazelRunner.commandBuilder().showRepo().executeBazelCommand(parseProcessOutput = false).waitAndGetResult(cancelChecker).statusCode == StatusCode.OK + companion object { private val InfoLinePattern = "([\\w-]+): (.*)".toRegex() } diff --git a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunner.kt b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunner.kt index 553f1b26a..efa9a82f8 100644 --- a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunner.kt +++ b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunner.kt @@ -28,7 +28,7 @@ class BazelRunner private constructor( fun commandBuilder(): BazelRunnerCommandBuilder = BazelRunnerCommandBuilder(this) fun runBazelCommandBes( - command: String, + command: List, flags: List, arguments: List, originId: String?, @@ -45,7 +45,7 @@ class BazelRunner private constructor( } fun runBazelCommand( - command: String, + command: List, flags: List, arguments: List, originId: String?, @@ -55,7 +55,7 @@ class BazelRunner private constructor( val workspaceContext = workspaceContextProvider.currentWorkspaceContext() val usedBuildFlags = if (useBuildFlags) buildFlags(workspaceContext) else emptyList() val processArgs = - listOf(bazel(workspaceContext), command) + usedBuildFlags + flags + arguments + listOf(bazel(workspaceContext)) + command + usedBuildFlags + flags + arguments logInvocation(processArgs, originId) val processBuilder = ProcessBuilder(processArgs) val outputLogger = bspClientLogger.takeIf { parseProcessOutput } diff --git a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerBuildBuilder.kt b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerBuildBuilder.kt index 3ff747c66..d64028e7e 100644 --- a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerBuildBuilder.kt +++ b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerBuildBuilder.kt @@ -6,7 +6,7 @@ import org.jetbrains.bsp.bazel.workspacecontext.TargetsSpec class BazelRunnerBuildBuilder( bazelRunner: BazelRunner, - bazelBuildCommand: String + bazelBuildCommand: List ) : BazelRunnerBuilder(bazelRunner, bazelBuildCommand) { override fun withTargets(bazelTargets: List): BazelRunnerBuilder { diff --git a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerBuilder.kt b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerBuilder.kt index 5755f55e9..9c5ebb7f9 100644 --- a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerBuilder.kt +++ b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerBuilder.kt @@ -9,7 +9,7 @@ import java.nio.file.Path open class BazelRunnerBuilder internal constructor( private val bazelRunner: BazelRunner, - private val bazelCommand: String + private val bazelCommand: List, ) { private val flags = mutableListOf() diff --git a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerCommandBuilder.kt b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerCommandBuilder.kt index ec03bc270..1d84eba86 100644 --- a/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerCommandBuilder.kt +++ b/bazelrunner/src/main/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelRunnerCommandBuilder.kt @@ -1,12 +1,20 @@ package org.jetbrains.bsp.bazel.bazelrunner class BazelRunnerCommandBuilder internal constructor(private val bazelRunner: BazelRunner) { - fun aquery() = BazelRunnerBuilder(bazelRunner, "aquery") - fun clean() = BazelRunnerBuilder(bazelRunner, "clean") - fun fetch() = BazelRunnerBuilder(bazelRunner, "fetch") - fun info() = BazelRunnerBuilder(bazelRunner, "info") - fun run() = BazelRunnerBuilder(bazelRunner, "run") - fun query() = BazelRunnerBuilder(bazelRunner, "query") - fun build() = BazelRunnerBuildBuilder(bazelRunner, "build") - fun test() = BazelRunnerBuildBuilder(bazelRunner, "test") + fun aquery() = BazelRunnerBuilder(bazelRunner, listOf("aquery")) + fun clean() = BazelRunnerBuilder(bazelRunner, listOf("clean")) + fun fetch() = BazelRunnerBuilder(bazelRunner, listOf("fetch")) + fun info() = BazelRunnerBuilder(bazelRunner, listOf("info")) + fun run() = BazelRunnerBuilder(bazelRunner, listOf("run")) + fun mod(subcommand: String) = BazelRunnerBuilder(bazelRunner, listOf("mod", subcommand)) + fun graph() = mod("graph") + fun deps() = mod("deps") + fun allPaths() = mod("all_paths") + fun path() = mod("path") + fun explain() = mod("explain") + fun showRepo() = mod("show_repo") + fun showExtension() = mod("show_extension") + fun query() = BazelRunnerBuilder(bazelRunner, listOf("query")) + fun build() = BazelRunnerBuildBuilder(bazelRunner, listOf("build")) + fun test() = BazelRunnerBuildBuilder(bazelRunner, listOf("test")) } diff --git a/bazelrunner/src/test/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelReleaseTest.kt b/bazelrunner/src/test/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelReleaseTest.kt index efdc7d702..786a66feb 100644 --- a/bazelrunner/src/test/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelReleaseTest.kt +++ b/bazelrunner/src/test/kotlin/org/jetbrains/bsp/bazel/bazelrunner/BazelReleaseTest.kt @@ -17,7 +17,7 @@ class BazelReleaseTest { // then release?.major shouldBe 4 - release?.mainRepositoryReferencePrefix() shouldBe "//" + release?.mainRepositoryReferencePrefix(false) shouldBe "//" } @Test @@ -27,7 +27,7 @@ class BazelReleaseTest { // then release?.major shouldBe 6 - release?.mainRepositoryReferencePrefix() shouldBe "@//" + release?.mainRepositoryReferencePrefix(false) shouldBe "@//" } @Test diff --git a/bazelrunner/src/test/kotlin/org/jetbrains/bsp/bazel/bazelrunner/StoredBazelInfoTest.kt b/bazelrunner/src/test/kotlin/org/jetbrains/bsp/bazel/bazelrunner/StoredBazelInfoTest.kt index 127b6fac0..f4f872094 100644 --- a/bazelrunner/src/test/kotlin/org/jetbrains/bsp/bazel/bazelrunner/StoredBazelInfoTest.kt +++ b/bazelrunner/src/test/kotlin/org/jetbrains/bsp/bazel/bazelrunner/StoredBazelInfoTest.kt @@ -34,10 +34,12 @@ class StoredBazelInfoTest { // when val bazelInfo = BasicBazelInfo( - "/private/var/tmp/_bazel/125c7a6ca879ed16a4b4b1a74bc5f27b/execroot/bazel_bsp", - Paths.get("/private/var/tmp/_bazel/125c7a6ca879ed16a4b4b1a74bc5f27b"), - Paths.get("/Users/user/workspace/bazel-bsp"), - BazelRelease(6)) + "/private/var/tmp/_bazel/125c7a6ca879ed16a4b4b1a74bc5f27b/execroot/bazel_bsp", + Paths.get("/private/var/tmp/_bazel/125c7a6ca879ed16a4b4b1a74bc5f27b"), + Paths.get("/Users/user/workspace/bazel-bsp"), + BazelRelease(6), + false + ) storage.store(bazelInfo) val loaded = storage.load() diff --git a/install/src/test/kotlin/org/jetbrains/bsp/bazel/install/EnvironmentCreatorTest.kt b/install/src/test/kotlin/org/jetbrains/bsp/bazel/install/EnvironmentCreatorTest.kt index 936c44ba1..1e370f95c 100644 --- a/install/src/test/kotlin/org/jetbrains/bsp/bazel/install/EnvironmentCreatorTest.kt +++ b/install/src/test/kotlin/org/jetbrains/bsp/bazel/install/EnvironmentCreatorTest.kt @@ -44,7 +44,7 @@ class EnvironmentCreatorTest { dotBazelBsp.resolve("aspects/utils/utils.bzl").exists() shouldBeEqual true dotBazelBsp.resolve("aspects/rules/java/java_info.bzl").exists() shouldBeEqual true dotBazelBsp.resolve("aspects/rules/jvm/jvm_info.bzl").exists() shouldBeEqual true - dotBazelBsp.resolve("aspects/rules/kt/kt_info.bzl").exists() shouldBeEqual true + dotBazelBsp.resolve("aspects/rules/kt/kt_info.bzl.template").exists() shouldBeEqual true dotBazelBsp.resolve("aspects/rules/python/python_info.bzl").exists() shouldBeEqual true dotBazelBsp.resolve("aspects/rules/scala/scala_info.bzl").exists() shouldBeEqual true dotBazelBsp.resolve("aspects/rules/cpp/cpp_info.bzl").exists() shouldBeEqual true diff --git a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD index c9b26ef31..f320e4f8b 100644 --- a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD +++ b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD @@ -19,6 +19,8 @@ kt_jvm_library( "@io_bazel//third_party/grpc:grpc-jar_checked_in", "@maven//:com_google_guava_guava", "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_velocity_velocity_engine_core", "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", + "@maven//:org_jetbrains_kotlinx_kotlinx_serialization_json", ], ) diff --git a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspEnvironmentManager.kt b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspEnvironmentManager.kt index 34ffd7791..e717f122d 100644 --- a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspEnvironmentManager.kt +++ b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspEnvironmentManager.kt @@ -1,93 +1,137 @@ package org.jetbrains.bsp.bazel.server.bsp.managers +import org.apache.velocity.VelocityContext +import org.apache.velocity.app.VelocityEngine import org.eclipse.lsp4j.jsonrpc.CancelChecker import org.jetbrains.bsp.bazel.commons.Constants import org.jetbrains.bsp.bazel.server.bsp.utils.InternalAspectsResolver +import java.io.StringWriter import java.nio.file.Paths +import java.util.Properties import kotlin.io.path.writeText -enum class Language(private val fileName: String, val functions: List) { - Java("//aspects:rules/java/java_info.bzl", listOf("extract_java_toolchain", "extract_java_runtime")), - Jvm("//aspects:rules/jvm/jvm_info.bzl", listOf("extract_jvm_info")), - Scala("//aspects:rules/scala/scala_info.bzl", listOf("extract_scala_info", "extract_scala_toolchain_info")), - Cpp("//aspects:rules/cpp/cpp_info.bzl", listOf("extract_cpp_info")), - Kotlin("//aspects:rules/kt/kt_info.bzl", listOf("extract_kotlin_info")), - Python("//aspects:rules/python/python_info.bzl", listOf("extract_python_info")); - - fun toLoadStatement(): String = - this.functions.joinToString( - prefix = """load("${this.fileName}", """, - separator = ", ", - postfix = ")" - ) { "\"$it\"" } +enum class Language(private val fileName: String, val functions: List, val isTemplate: Boolean) { + Java("//aspects:rules/java/java_info.bzl", listOf("extract_java_toolchain", "extract_java_runtime"), false), + Jvm("//aspects:rules/jvm/jvm_info.bzl", listOf("extract_jvm_info"), false), + Scala("//aspects:rules/scala/scala_info.bzl", listOf("extract_scala_info", "extract_scala_toolchain_info"), false), + Cpp("//aspects:rules/cpp/cpp_info.bzl", listOf("extract_cpp_info"), false), + Kotlin("//aspects:rules/kt/kt_info.bzl", listOf("extract_kotlin_info"), true), + Python("//aspects:rules/python/python_info.bzl", listOf("extract_python_info"), false); + + fun toLoadStatement(): String = + this.functions.joinToString( + prefix = """load("${this.fileName}", """, + separator = ", ", + postfix = ")" + ) { "\"$it\"" } + + fun toAspectRelativePath(): String = + fileName.substringAfter(":") + + fun toAspectTemplateRelativePath(): String = + "${toAspectRelativePath()}.template" } class BazelBspEnvironmentManager( - private val internalAspectsResolver: InternalAspectsResolver, - private val bazelExternalRulesQuery: BazelExternalRulesQuery, + private val internalAspectsResolver: InternalAspectsResolver, + private val bazelExternalRulesQuery: BazelExternalRulesQuery, ) { - fun generateLanguageExtensions(cancelChecker: CancelChecker) { - val allExternalRuleNames = bazelExternalRulesQuery.fetchExternalRuleNames(cancelChecker) - val languages = getProjectLanguages(allExternalRuleNames) - val fileContent = prepareFileContent(languages) + private val aspectsPath = Paths.get(internalAspectsResolver.bazelBspRoot, Constants.ASPECTS_ROOT) + private val velocityEngine = VelocityEngine() - createNewExtensionsFile(fileContent) - } + init { + val props = calculateProperties() + velocityEngine.init(props) + } - private fun getProjectLanguages(allExternalRuleNames: List): List { - data class RuleLanguage(val ruleNames: List, val language: Language) - - val ruleLanguages = listOf( - RuleLanguage(listOf(), Language.Java), // Bundled in Bazel - RuleLanguage(listOf(), Language.Jvm), // Bundled in Bazel - RuleLanguage(listOf("rules_python"), Language.Python), - RuleLanguage(listOf("rules_cc"), Language.Cpp), - RuleLanguage(listOf("io_bazel_rules_kotlin"), Language.Kotlin), - RuleLanguage(listOf("io_bazel_rules_scala"), Language.Scala) - ) - - return ruleLanguages.mapNotNull { (ruleNames, language) -> - if (ruleNames.isEmpty() || ruleNames.any { allExternalRuleNames.contains(it) }) language else null - } - } + private fun calculateProperties(): Properties { + val props = Properties() + props["file.resource.loader.path"] = aspectsPath.toAbsolutePath().toString() + props.setProperty("runtime.log.logsystem.class", "org.apache.velocity.runtime.log.NullLogSystem") + return props + } + + private data class RuleLanguage(val ruleNames: List, val language: Language) + + fun generateLanguageExtensions(cancelChecker: CancelChecker) { + val allExternalRuleNames = bazelExternalRulesQuery.fetchExternalRuleNames(cancelChecker) + val ruleLanguages = getProjectRuleLanguages(allExternalRuleNames) + makeAspectFilesFromTemplates(ruleLanguages) + + val fileContent = prepareFileContent(ruleLanguages) - private fun prepareFileContent(languages: List) = - listOf( - "# This is a generated file, do not edit it", - createLoadStatementsString(languages), - createExtensionListString(languages), - createToolchainListString(languages) - ).joinToString( - separator = "\n", - postfix = "\n" - ) - - private fun createLoadStatementsString(languages: List): String { - val loadStatements = languages.map { it.toLoadStatement() } - return loadStatements.joinToString(postfix = "\n", separator = "\n") + createNewExtensionsFile(fileContent) + } + + private fun getProjectRuleLanguages(allExternalRuleNames: List): List { + + val ruleLanguages = listOf( + RuleLanguage(listOf(), Language.Java), // Bundled in Bazel + RuleLanguage(listOf(), Language.Jvm), // Bundled in Bazel + RuleLanguage(listOf("rules_python"), Language.Python), + RuleLanguage(listOf("rules_cc"), Language.Cpp), + RuleLanguage(listOf("io_bazel_rules_kotlin", "rules_kotlin"), Language.Kotlin), + RuleLanguage(listOf("io_bazel_rules_scala"), Language.Scala) + ) + + return ruleLanguages.mapNotNull { (ruleNames, language) -> + if (ruleNames.isEmpty()) return@mapNotNull RuleLanguage(listOf(), language) + val ruleName = ruleNames.firstOrNull { allExternalRuleNames.contains(it) } + if (ruleName != null) + RuleLanguage(listOf(ruleName), language) + else null } + } - private fun createExtensionListString(languages: List): String { - val functionNames = languages.flatMap { it.functions } - return functionNames.joinToString(prefix = "EXTENSIONS = [\n", postfix = "\n]", separator = ",\n ") { "\t$it" } + private fun makeAspectFilesFromTemplates(ruleLanguages: List) { + ruleLanguages.forEach { + if (it.ruleNames.isEmpty() || !it.language.isTemplate) return@forEach + val file = aspectsPath.resolve(it.language.toAspectRelativePath()) + val template = velocityEngine.getTemplate(it.language.toAspectTemplateRelativePath()) + val context = VelocityContext() + context.put("ruleName", it.ruleNames.first()) + val writer = StringWriter() + template.merge(context, writer) + file.writeText(writer.toString()) } + } + + private fun prepareFileContent(ruleLanguages: List) = + listOf( + "# This is a generated file, do not edit it", + createLoadStatementsString(ruleLanguages.map { it.language }), + createExtensionListString(ruleLanguages.map { it.language }), + createToolchainListString(ruleLanguages) + ).joinToString( + separator = "\n", + postfix = "\n" + ) - private fun createToolchainListString(languages: List): String = - languages.mapNotNull { - when (it) { - Language.Scala -> """"@io_bazel_rules_scala//scala:toolchain_type"""" - Language.Java -> """"@bazel_tools//tools/jdk:runtime_toolchain_type"""" - Language.Kotlin -> """"@io_bazel_rules_kotlin//kotlin/internal:kt_toolchain_type"""" - else -> null - } - } - .joinToString(prefix = "TOOLCHAINS = [\n", postfix = "\n]", separator = ",\n ") { "\t$it" } - - - private fun createNewExtensionsFile(fileContent: String) { - val aspectsPath = Paths.get(internalAspectsResolver.bazelBspRoot, Constants.ASPECTS_ROOT) - val file = aspectsPath.resolve("extensions.bzl") - file.writeText(fileContent) + private fun createLoadStatementsString(languages: List): String { + val loadStatements = languages.map { it.toLoadStatement() } + return loadStatements.joinToString(postfix = "\n", separator = "\n") + } + + private fun createExtensionListString(languages: List): String { + val functionNames = languages.flatMap { it.functions } + return functionNames.joinToString(prefix = "EXTENSIONS = [\n", postfix = "\n]", separator = ",\n ") { "\t$it" } + } + + private fun createToolchainListString(ruleLanguages: List): String = + ruleLanguages.mapNotNull { + when (it.language) { + Language.Scala -> """"@${it.ruleNames.first()}//scala:toolchain_type"""" + Language.Java -> """"@bazel_tools//tools/jdk:runtime_toolchain_type"""" + Language.Kotlin -> """"@${it.ruleNames.first()}//kotlin/internal:kt_toolchain_type"""" + else -> null + } } + .joinToString(prefix = "TOOLCHAINS = [\n", postfix = "\n]", separator = ",\n ") { "\t$it" } + + + private fun createNewExtensionsFile(fileContent: String) { + val file = aspectsPath.resolve("extensions.bzl") + file.writeText(fileContent) + } } diff --git a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelExternalRulesQuery.kt b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelExternalRulesQuery.kt index 29947bddd..f317d1b6a 100644 --- a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelExternalRulesQuery.kt +++ b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelExternalRulesQuery.kt @@ -1,5 +1,12 @@ package org.jetbrains.bsp.bazel.server.bsp.managers +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.contentOrNull +import org.apache.logging.log4j.LogManager import org.eclipse.lsp4j.jsonrpc.CancelChecker import org.jetbrains.bsp.bazel.bazelrunner.BazelRunner import org.jetbrains.bsp.bazel.commons.escapeNewLines @@ -12,37 +19,94 @@ import javax.xml.xpath.XPathConstants import javax.xml.xpath.XPathFactory interface BazelExternalRulesQuery { - fun fetchExternalRuleNames(cancelChecker: CancelChecker): List + fun fetchExternalRuleNames(cancelChecker: CancelChecker): List } -class BazelExternalRulesQueryImpl(private val bazelRunner: BazelRunner) : BazelExternalRulesQuery { - - override fun fetchExternalRuleNames(cancelChecker: CancelChecker): List = - bazelRunner.commandBuilder().query() - .withArgument("//external:*") - .withFlags(listOf("--output=xml", "--order_output=no")) - .executeBazelCommand(parseProcessOutput = false, useBuildFlags = false) - .waitAndGetResult(cancelChecker, ensureAllOutputRead = true).let { result -> - if (result.isNotSuccess) - error("Bazel query failed with output: '${result.stderr.escapeNewLines()}'") - else - result.stdout.readXML()?.calculateEligibleRules() - } ?: listOf() - - private fun String.readXML(): Document? = - DocumentBuilderFactory - .newInstance() - .newDocumentBuilder() - .parse(InputSource(StringReader(this))) - - private fun Document.calculateEligibleRules(): List { - val xPath = XPathFactory.newInstance().newXPath() - val expression = "/query/rule[not(.//string[@name='generator_function'])]//string[@name='name']" - val eligibleItems = xPath.evaluate(expression, this, XPathConstants.NODESET) as NodeList - val returnList = mutableListOf() - for (i in 0 until eligibleItems.length) { - eligibleItems.item(i).attributes.getNamedItem("value")?.nodeValue?.let { returnList.add(it) } +class BazelExternalRulesQueryImpl( + private val bazelRunner: BazelRunner, + private val isBzlModEnabled: Boolean) : BazelExternalRulesQuery { + + override fun fetchExternalRuleNames(cancelChecker: CancelChecker): List = + fetchWorkspaceExternalRuleNames(cancelChecker) + fetchBzlModExternalRuleNames(cancelChecker) + + private fun fetchWorkspaceExternalRuleNames(cancelChecker: CancelChecker): List = + bazelRunner.commandBuilder().query() + .withArgument("//external:*") + .withFlags(listOf("--output=xml", "--order_output=no")) + .executeBazelCommand(parseProcessOutput = false, useBuildFlags = false) + .waitAndGetResult(cancelChecker, ensureAllOutputRead = true).let { result -> + if (result.isNotSuccess) + error("Bazel query failed with output: '${result.stderr.escapeNewLines()}'") + else + result.stdout.readXML()?.calculateEligibleRules() + } ?: listOf() + + private fun String.readXML(): Document? = try { + DocumentBuilderFactory + .newInstance() + .newDocumentBuilder() + .parse(InputSource(StringReader(this))) + } catch (e: Exception) { + log.error("Failed to parse string to xml", e) + null + } + + private fun Document.calculateEligibleRules(): List { + val xPath = XPathFactory.newInstance().newXPath() + val expression = "/query/rule[not(.//string[@name='generator_function'])]//string[@name='name']" + val eligibleItems = xPath.evaluate(expression, this, XPathConstants.NODESET) as NodeList + val returnList = mutableListOf() + for (i in 0 until eligibleItems.length) { + eligibleItems.item(i).attributes.getNamedItem("value")?.nodeValue?.let { returnList.add(it) } + } + return returnList.toList() + } + + private fun fetchBzlModExternalRuleNames(cancelChecker: CancelChecker): List = + if (!isBzlModEnabled) listOf() + else { + val jsonElement = bazelRunner.commandBuilder().graph() + .withFlag("--output=json") + .executeBazelCommand(parseProcessOutput = false) + .waitAndGetResult(cancelChecker, ensureAllOutputRead = true).let { result -> + if (result.isNotSuccess) + error("Bazel query failed with output: '${result.stderr.escapeNewLines()}'") + else result.stdout.toJson() } - return returnList.toList() + extractValuesFromKey(jsonElement, "key") + .map { it.substringBefore('@') } // the element has the format @ + .distinct() } + + private fun String.toJson(): JsonElement? = try { + Json.parseToJsonElement(this) + } catch (e: Exception) { + log.error("Failed to parse string to json", e) + null + } + + private fun extractValuesFromKey(jsonElement: JsonElement?, key: String): MutableList { + val res = mutableListOf() + extractValuesFromKeyRecursively(jsonElement, key, res) + return res + } + + private fun extractValuesFromKeyRecursively(jsonElement: JsonElement?, key: String, res: MutableList) { + when (jsonElement) { + is JsonObject -> jsonElement.entries.forEach { (k, v) -> + if ((k == key) && v is JsonPrimitive && v.contentOrNull != null) { + res.add(v.content) + } else { + extractValuesFromKeyRecursively(v, key, res) + } + } + + is JsonArray -> jsonElement.forEach { extractValuesFromKeyRecursively(it, key, res) } + else -> {} + } + } + + companion object { + private val log = LogManager.getLogger(BazelExternalRulesQueryImpl::class.java) + } } diff --git a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/common/ServerContainer.kt b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/common/ServerContainer.kt index 57dc4e63b..c58f89b47 100644 --- a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/common/ServerContainer.kt +++ b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/common/ServerContainer.kt @@ -14,7 +14,14 @@ import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspEnvironmentManager import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspFallbackAspectsManager import org.jetbrains.bsp.bazel.server.bsp.managers.BazelExternalRulesQueryImpl import org.jetbrains.bsp.bazel.server.bsp.utils.InternalAspectsResolver -import org.jetbrains.bsp.bazel.server.sync.* +import org.jetbrains.bsp.bazel.server.sync.BazelPathsResolver +import org.jetbrains.bsp.bazel.server.sync.BazelProjectMapper +import org.jetbrains.bsp.bazel.server.sync.FileProjectStorage +import org.jetbrains.bsp.bazel.server.sync.ProjectProvider +import org.jetbrains.bsp.bazel.server.sync.ProjectResolver +import org.jetbrains.bsp.bazel.server.sync.ProjectStorage +import org.jetbrains.bsp.bazel.server.sync.TargetInfoReader +import org.jetbrains.bsp.bazel.server.sync.TargetKindResolver import org.jetbrains.bsp.bazel.server.sync.languages.LanguagePluginsService import org.jetbrains.bsp.bazel.server.sync.languages.cpp.CppLanguagePlugin import org.jetbrains.bsp.bazel.server.sync.languages.java.JavaLanguagePlugin @@ -35,66 +42,66 @@ class ServerContainer internal constructor( val compilationManager: BazelBspCompilationManager, val languagePluginsService: LanguagePluginsService ) { - companion object { - @JvmStatic - fun create( - bspInfo: BspInfo, - workspaceContextProvider: WorkspaceContextProvider, - projectStorage: ProjectStorage?, - bspClientLogger: BspClientLogger, - bspClientTestNotifier: BspClientTestNotifier, - bazelRunner: BazelRunner, - compilationManager: BazelBspCompilationManager, - metricsLogger: MetricsLogger? - ): ServerContainer { - val bazelInfoStorage = BazelInfoStorage(bspInfo) - val bazelDataResolver = - BazelInfoResolver( - bazelRunner, - bazelInfoStorage - ) - val bazelInfo = bazelDataResolver.resolveBazelInfo { } + companion object { + @JvmStatic + fun create( + bspInfo: BspInfo, + workspaceContextProvider: WorkspaceContextProvider, + projectStorage: ProjectStorage?, + bspClientLogger: BspClientLogger, + bspClientTestNotifier: BspClientTestNotifier, + bazelRunner: BazelRunner, + compilationManager: BazelBspCompilationManager, + metricsLogger: MetricsLogger? + ): ServerContainer { + val bazelInfoStorage = BazelInfoStorage(bspInfo) + val bazelDataResolver = + BazelInfoResolver( + bazelRunner, + bazelInfoStorage + ) + val bazelInfo = bazelDataResolver.resolveBazelInfo { } - val aspectsResolver = InternalAspectsResolver(bspInfo, bazelInfo.release) - val bazelBspEnvironmentManager = BazelBspEnvironmentManager(aspectsResolver, BazelExternalRulesQueryImpl(bazelRunner)) - val bazelBspFallbackAspectsManager = BazelBspFallbackAspectsManager(bazelRunner, workspaceContextProvider) - val bazelBspAspectsManager = BazelBspAspectsManager(compilationManager, aspectsResolver, bazelBspEnvironmentManager) - val bazelPathsResolver = BazelPathsResolver(bazelInfo) - val jdkResolver = JdkResolver(bazelPathsResolver, JdkVersionResolver()) - val javaLanguagePlugin = JavaLanguagePlugin(bazelPathsResolver, jdkResolver, bazelInfo) - val scalaLanguagePlugin = ScalaLanguagePlugin(javaLanguagePlugin, bazelPathsResolver) - val cppLanguagePlugin = CppLanguagePlugin(bazelPathsResolver) - val kotlinLanguagePlugin = KotlinLanguagePlugin(javaLanguagePlugin) - val thriftLanguagePlugin = ThriftLanguagePlugin(bazelPathsResolver) - val pythonLanguagePlugin = PythonLanguagePlugin(bazelPathsResolver) - val languagePluginsService = LanguagePluginsService( - scalaLanguagePlugin, javaLanguagePlugin, cppLanguagePlugin, kotlinLanguagePlugin, thriftLanguagePlugin, pythonLanguagePlugin - ) - val targetKindResolver = TargetKindResolver() - val bazelProjectMapper = - BazelProjectMapper(languagePluginsService, bazelPathsResolver, targetKindResolver, bazelInfo, bspClientLogger, metricsLogger) - val targetInfoReader = TargetInfoReader() - val projectResolver = ProjectResolver( - bazelBspAspectsManager, - bazelBspFallbackAspectsManager, - workspaceContextProvider, - bazelProjectMapper, - bspClientLogger, - targetInfoReader, - bazelInfo, - metricsLogger - ) - val finalProjectStorage = projectStorage ?: FileProjectStorage(bspInfo, bspClientLogger) - val projectProvider = ProjectProvider(projectResolver, finalProjectStorage) - return ServerContainer( - projectProvider, - bspClientLogger, - bspClientTestNotifier, - bazelInfo, - bazelRunner, - compilationManager, - languagePluginsService - ) - } + val aspectsResolver = InternalAspectsResolver(bspInfo, bazelInfo.release) + val bazelBspEnvironmentManager = BazelBspEnvironmentManager(aspectsResolver, BazelExternalRulesQueryImpl(bazelRunner, bazelInfo.isBzlModEnabled)) + val bazelBspFallbackAspectsManager = BazelBspFallbackAspectsManager(bazelRunner, workspaceContextProvider) + val bazelBspAspectsManager = BazelBspAspectsManager(compilationManager, aspectsResolver, bazelBspEnvironmentManager) + val bazelPathsResolver = BazelPathsResolver(bazelInfo) + val jdkResolver = JdkResolver(bazelPathsResolver, JdkVersionResolver()) + val javaLanguagePlugin = JavaLanguagePlugin(bazelPathsResolver, jdkResolver, bazelInfo) + val scalaLanguagePlugin = ScalaLanguagePlugin(javaLanguagePlugin, bazelPathsResolver) + val cppLanguagePlugin = CppLanguagePlugin(bazelPathsResolver) + val kotlinLanguagePlugin = KotlinLanguagePlugin(javaLanguagePlugin) + val thriftLanguagePlugin = ThriftLanguagePlugin(bazelPathsResolver) + val pythonLanguagePlugin = PythonLanguagePlugin(bazelPathsResolver) + val languagePluginsService = LanguagePluginsService( + scalaLanguagePlugin, javaLanguagePlugin, cppLanguagePlugin, kotlinLanguagePlugin, thriftLanguagePlugin, pythonLanguagePlugin + ) + val targetKindResolver = TargetKindResolver() + val bazelProjectMapper = + BazelProjectMapper(languagePluginsService, bazelPathsResolver, targetKindResolver, bazelInfo, bspClientLogger, metricsLogger) + val targetInfoReader = TargetInfoReader() + val projectResolver = ProjectResolver( + bazelBspAspectsManager, + bazelBspFallbackAspectsManager, + workspaceContextProvider, + bazelProjectMapper, + bspClientLogger, + targetInfoReader, + bazelInfo, + metricsLogger + ) + val finalProjectStorage = projectStorage ?: FileProjectStorage(bspInfo, bspClientLogger) + val projectProvider = ProjectProvider(projectResolver, finalProjectStorage) + return ServerContainer( + projectProvider, + bspClientLogger, + bspClientTestNotifier, + bazelInfo, + bazelRunner, + compilationManager, + languagePluginsService + ) } + } } diff --git a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/BazelPathsResolver.kt b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/BazelPathsResolver.kt index 0d6ea5d84..72c191384 100644 --- a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/BazelPathsResolver.kt +++ b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/BazelPathsResolver.kt @@ -79,7 +79,7 @@ class BazelPathsResolver(private val bazelInfo: BazelInfo) { } fun extractRelativePath(label: String): String { - val prefix = bazelInfo.release.mainRepositoryReferencePrefix() + val prefix = bazelInfo.release.mainRepositoryReferencePrefix(bazelInfo.isBzlModEnabled) require(label.startsWith(prefix)) { String.format( "%s didn't start with %s", label, prefix diff --git a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/BazelProjectMapper.kt b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/BazelProjectMapper.kt index 0578e7efd..a593763d9 100644 --- a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/BazelProjectMapper.kt +++ b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/BazelProjectMapper.kt @@ -296,7 +296,7 @@ class BazelProjectMapper( } private fun isWorkspaceTarget(target: TargetInfo): Boolean = - target.id.startsWith(bazelInfo.release.mainRepositoryReferencePrefix()) && + target.id.startsWith(bazelInfo.release.mainRepositoryReferencePrefix(bazelInfo.isBzlModEnabled)) && (hasJavaSources(target) || target.kind in setOf("java_library", "java_binary", @@ -424,7 +424,7 @@ class BazelProjectMapper( targetInfo.envInheritList.associateWith { System.getenv(it) } private fun removeDotBazelBspTarget(targets: List): List { - val prefix = bazelInfo.release.mainRepositoryReferencePrefix() + ".bazelbsp" + val prefix = bazelInfo.release.mainRepositoryReferencePrefix(bazelInfo.isBzlModEnabled) + ".bazelbsp" return targets.filter { !it.startsWith(prefix) } } } diff --git a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/ProjectResolver.kt b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/ProjectResolver.kt index 8ba1cc20a..b7961bcb3 100644 --- a/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/ProjectResolver.kt +++ b/server/src/main/kotlin/org/jetbrains/bsp/bazel/server/sync/ProjectResolver.kt @@ -3,77 +3,79 @@ package org.jetbrains.bsp.bazel.server.sync import org.eclipse.lsp4j.jsonrpc.CancelChecker import org.jetbrains.bsp.bazel.bazelrunner.BazelInfo import org.jetbrains.bsp.bazel.logger.BspClientLogger -import org.jetbrains.bsp.bazel.server.bep.BepOutput -import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspFallbackAspectsManager import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspAspectsManager import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspAspectsManagerResult +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspFallbackAspectsManager import org.jetbrains.bsp.bazel.server.sync.model.Project import org.jetbrains.bsp.bazel.workspacecontext.WorkspaceContext import org.jetbrains.bsp.bazel.workspacecontext.WorkspaceContextProvider /** Responsible for querying bazel and constructing Project instance */ class ProjectResolver( - private val bazelBspAspectsManager: BazelBspAspectsManager, - private val bazelBspFallbackAspectsManager: BazelBspFallbackAspectsManager, - private val workspaceContextProvider: WorkspaceContextProvider, - private val bazelProjectMapper: BazelProjectMapper, - private val bspLogger: BspClientLogger, - private val targetInfoReader: TargetInfoReader, - private val bazelInfo: BazelInfo, - private val metricsLogger: MetricsLogger? + private val bazelBspAspectsManager: BazelBspAspectsManager, + private val bazelBspFallbackAspectsManager: BazelBspFallbackAspectsManager, + private val workspaceContextProvider: WorkspaceContextProvider, + private val bazelProjectMapper: BazelProjectMapper, + private val bspLogger: BspClientLogger, + private val targetInfoReader: TargetInfoReader, + private val bazelInfo: BazelInfo, + private val metricsLogger: MetricsLogger? ) { - private fun measured(description: String, f: () -> T): T { - return Measurements.measure(f, description, metricsLogger, bspLogger) - } + private fun measured(description: String, f: () -> T): T { + return Measurements.measure(f, description, metricsLogger, bspLogger) + } - fun resolve(cancelChecker: CancelChecker): Project { + fun resolve(cancelChecker: CancelChecker): Project { - val workspaceContext = measured( - "Reading project view and creating workspace context", - workspaceContextProvider::currentWorkspaceContext - ) - val buildAspectResult = measured( - "Building project with aspect" - ) { buildProjectWithAspect(cancelChecker, workspaceContext) } - val allTargetNames = - if (buildAspectResult.isFailure) - measured( - "Fetching all possible target names" - ) { bazelBspFallbackAspectsManager.getAllPossibleTargets(cancelChecker).let { formatTargetsIfNeeded(it) } } - else - emptyList() - val aspectOutputs = measured( - "Reading aspect output paths" - ) { buildAspectResult.bepOutput.filesByOutputGroupNameTransitive(BSP_INFO_OUTPUT_GROUP) } - val rootTargets = buildAspectResult.bepOutput.rootTargets().let { formatTargetsIfNeeded(it) } - val targets = measured( - "Parsing aspect outputs" - ) { targetInfoReader.readTargetMapFromAspectOutputs(aspectOutputs) } - return measured( - "Mapping to internal model" - ) { bazelProjectMapper.createProject(targets, rootTargets.toSet(), allTargetNames, workspaceContext) } - } + val workspaceContext = measured( + "Reading project view and creating workspace context", + workspaceContextProvider::currentWorkspaceContext + ) + val buildAspectResult = measured( + "Building project with aspect" + ) { buildProjectWithAspect(cancelChecker, workspaceContext) } + val allTargetNames = + if (buildAspectResult.isFailure) + measured( + "Fetching all possible target names" + ) { bazelBspFallbackAspectsManager.getAllPossibleTargets(cancelChecker).let { formatTargetsIfNeeded(it) } } + else + emptyList() + val aspectOutputs = measured( + "Reading aspect output paths" + ) { buildAspectResult.bepOutput.filesByOutputGroupNameTransitive(BSP_INFO_OUTPUT_GROUP) } + val rootTargets = buildAspectResult.bepOutput.rootTargets().let { formatTargetsIfNeeded(it) } + val targets = measured( + "Parsing aspect outputs" + ) { targetInfoReader.readTargetMapFromAspectOutputs(aspectOutputs) } + return measured( + "Mapping to internal model" + ) { bazelProjectMapper.createProject(targets, rootTargets.toSet(), allTargetNames, workspaceContext) } + } - private fun buildProjectWithAspect(cancelChecker: CancelChecker, workspaceContext: WorkspaceContext): BazelBspAspectsManagerResult = - bazelBspAspectsManager.fetchFilesFromOutputGroups( - cancelChecker, - workspaceContext.targets, - ASPECT_NAME, - listOf(BSP_INFO_OUTPUT_GROUP, ARTIFACTS_OUTPUT_GROUP) - ) + private fun buildProjectWithAspect(cancelChecker: CancelChecker, workspaceContext: WorkspaceContext): BazelBspAspectsManagerResult = + bazelBspAspectsManager.fetchFilesFromOutputGroups( + cancelChecker, + workspaceContext.targets, + ASPECT_NAME, + listOf(BSP_INFO_OUTPUT_GROUP, ARTIFACTS_OUTPUT_GROUP) + ) - private fun formatTargetsIfNeeded(targets: Collection): List = - when(bazelInfo.release.major){ - // Since bazel 6, the main repository targets are stringified to "@//"-prefixed labels, - // contrary to "//"-prefixed in older Bazel versions. Unfortunately this does not apply - // to BEP data, probably due to a bug, so we need to add the "@" prefix here. - in 0..5 -> targets.toList() - else -> targets.map { "@$it" } - }.toList() + private fun formatTargetsIfNeeded(targets: Collection): List = + when (bazelInfo.release.major) { + // Since bazel 6, the main repository targets are stringified to "@//"-prefixed labels, + // contrary to "//"-prefixed in older Bazel versions. Unfortunately this does not apply + // to BEP data, probably due to a bug, so we need to add the "@" prefix here. + in 0..5 -> targets.toList() + else -> + if (bazelInfo.isBzlModEnabled) + targets.map { "@@$it" } + else targets.map { "@$it" } + }.toList() - companion object { - private const val ASPECT_NAME = "bsp_target_info_aspect" - private const val BSP_INFO_OUTPUT_GROUP = "bsp-target-info-transitive-deps" - private const val ARTIFACTS_OUTPUT_GROUP = "external-deps-resolve-transitive-deps" - } + companion object { + private const val ASPECT_NAME = "bsp_target_info_aspect" + private const val BSP_INFO_OUTPUT_GROUP = "bsp-target-info-transitive-deps" + private const val ARTIFACTS_OUTPUT_GROUP = "external-deps-resolve-transitive-deps" + } } diff --git a/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD b/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD index 7fb2c3772..8e8892f25 100644 --- a/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD +++ b/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD @@ -6,6 +6,7 @@ kt_test( src = "BazelBspEnvironmentManagerTest.kt", deps = [ "//commons", + "//install:install-lib", "//server/src/main/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers", "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", ], diff --git a/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspEnvironmentManagerTest.kt b/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspEnvironmentManagerTest.kt index 5d288d362..094d28c32 100644 --- a/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspEnvironmentManagerTest.kt +++ b/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspEnvironmentManagerTest.kt @@ -2,18 +2,25 @@ package org.jetbrains.bsp.bazel.server.bsp.managers import io.kotest.matchers.equals.shouldBeEqual import org.eclipse.lsp4j.jsonrpc.CancelChecker -import org.jetbrains.bsp.bazel.bazelrunner.BazelInfo import org.jetbrains.bsp.bazel.bazelrunner.BazelRelease +import org.jetbrains.bsp.bazel.install.EnvironmentCreator import org.jetbrains.bsp.bazel.server.bsp.info.BspInfo import org.jetbrains.bsp.bazel.server.bsp.utils.InternalAspectsResolver -import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import java.nio.file.Path -import kotlin.io.path.createDirectory import kotlin.io.path.createTempDirectory +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class BazelBspEnvironmentManagerTest { + class MockEnvironmentCreator(projectRootDir: Path) : EnvironmentCreator(projectRootDir) { + override fun create(): Unit = Unit + + fun testCreateDotBazelBsp() = createDotBazelBsp() + } + internal class BspInfoMock(private val dotBazelBspPath: Path) : BspInfo() { override fun bazelBspDir(): Path = dotBazelBspPath } @@ -57,11 +64,14 @@ class BazelBspEnvironmentManagerTest { .joinToString(separator = "") .filterNot { it.isWhitespace() } - @BeforeEach + @BeforeAll fun before() { - val dotBazelBspPath = createTempDirectory(".bazelbsp") + val tempRoot = createTempDirectory("test-workspace-root") + tempRoot.toFile().deleteOnExit() + val dotBazelBspPath = MockEnvironmentCreator(tempRoot).testCreateDotBazelBsp() + - dotBazelBspAspectsPath = dotBazelBspPath.resolve("aspects").createDirectory() + dotBazelBspAspectsPath = dotBazelBspPath.resolve("aspects") internalAspectsResolverMock = InternalAspectsResolver(BspInfoMock(dotBazelBspPath), BazelRelease(5)) } diff --git a/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePluginServiceTest.kt b/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePluginServiceTest.kt index 84c167163..117a733c8 100644 --- a/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePluginServiceTest.kt +++ b/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/sync/languages/LanguagePluginServiceTest.kt @@ -37,6 +37,7 @@ class LanguagePluginServiceTest { outputBase = Paths.get(""), workspaceRoot = Paths.get(""), release = BazelRelease.fromReleaseString("release 6.0.0").orLatestSupported(), + false ) val bazelPathsResolver = BazelPathsResolver(bazelInfo) val jdkResolver = JdkResolver(bazelPathsResolver, JdkVersionResolver()) diff --git a/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/sync/languages/java/IdeClasspathResolverTest.kt b/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/sync/languages/java/IdeClasspathResolverTest.kt index 8de5a425d..40163e5fc 100644 --- a/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/sync/languages/java/IdeClasspathResolverTest.kt +++ b/server/src/test/kotlin/org/jetbrains/bsp/bazel/server/sync/languages/java/IdeClasspathResolverTest.kt @@ -21,10 +21,11 @@ class IdeClasspathResolverTest { fun beforeEach() { // given val bazelInfo = BasicBazelInfo( - execRoot = execRoot, - outputBase = Paths.get(outputBase), - workspaceRoot = Paths.get("/Users/user/workspace/bazel-bsp"), - release = BazelRelease.fromReleaseString("release 6.0.0").orLatestSupported() + execRoot = execRoot, + outputBase = Paths.get(outputBase), + workspaceRoot = Paths.get("/Users/user/workspace/bazel-bsp"), + release = BazelRelease.fromReleaseString("release 6.0.0").orLatestSupported(), + false ) bazelPathsResolver = BazelPathsResolver(bazelInfo)