Skip to content
This repository has been archived by the owner on Aug 5, 2024. It is now read-only.

Commit

Permalink
[feature] Install bazelisk if bazel is not found
Browse files Browse the repository at this point in the history
Adds a new e2e test

Merge-request: BAZEL-MR-479
Merged-by: Andrzej Gluszak <[email protected]>
  • Loading branch information
agluszak authored and Space Team committed Oct 27, 2023
1 parent 14a117f commit 89beaf7
Show file tree
Hide file tree
Showing 16 changed files with 235 additions and 81 deletions.
4 changes: 4 additions & 0 deletions .teamcity/configurations/bazelBsp/bazelE2eTests.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ object SampleRepoBazel6E2ETest : BazelBspE2ETestsBuildType(
targets = "//e2e:sample_repo_test_bazel_6_3_2",
)

object ServerDownloadsBazeliskTest : BazelBspE2ETestsBuildType(
targets = "//e2e:server_downloads_bazelisk_test_bazel_6_3_2",
)

object SampleRepoBazel5E2ETest : BazelBspE2ETestsBuildType(
targets = "//e2e:sample_repo_test_bazel_5_3_2",
)
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

## [Unreleased]

### Features
- The server will now download bazelisk if Bazel is not found in the PATH.

### Fixes 🛠️

- Project cache correctly deserializes kotlin modules.
Expand Down
1 change: 1 addition & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ maven_install(
"com.google.code.gson:gson:2.10.1",
"com.google.guava:guava:31.0.1-jre",
"ch.epfl.scala:bsp4j_2.13:2.1.0-M6.alpha",
"commons-io:commons-io:jar:2.15.0",
"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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ kt_jvm_library(
deps = [
"@maven//:ch_epfl_scala_bsp4j_2_13",
"@maven//:com_google_guava_guava",
"@maven//:org_apache_logging_log4j_log4j_api",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.jetbrains.bsp.bazel.commons

import java.io.File

object FileUtils {
fun getCacheDirectory(subfolder: String): File? {
val path = System.getenv("XDG_CACHE_HOME") ?: run {
val os = System.getProperty("os.name").lowercase()
when {
os.startsWith("windows") ->
System.getenv("LOCALAPPDATA") ?: System.getenv("APPDATA")
os.startsWith("linux") ->
System.getenv("HOME") + "/.cache"
os.startsWith("mac") ->
System.getenv("HOME") + "/Library/Caches"
else -> return null
}
}
val file = File(path, subfolder)
try {
file.mkdirs()
} catch (e: Exception) {
return null
}
if (!file.exists() || !file.isDirectory) {
return null
}
return file
}
}
11 changes: 11 additions & 0 deletions e2e/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,14 @@ bazel_integration_tests(
test_runner = "//e2e/src/main/kotlin/org/jetbrains/bsp/bazel:BazelBspPythonProjectTest",
workspace_path = "test-resources/python-project",
)

bazel_integration_tests(
name = "server_downloads_bazelisk_test",
timeout = "eternal",
bazel_versions = bazel_binaries.versions.all,
env = {
"PATH": "", # To ensure that the server won't find Bazel in PATH
},
test_runner = "//e2e/src/main/kotlin/org/jetbrains/bsp/bazel:ServerDownloadsBazeliskTest",
workspace_path = "test-resources/sample-repo",
)
13 changes: 13 additions & 0 deletions e2e/src/main/kotlin/org/jetbrains/bsp/bazel/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,16 @@ kt_jvm_binary(
"@maven//:ch_epfl_scala_bsp4j_2_13",
],
)

kt_jvm_binary(
name = "ServerDownloadsBazeliskTest",
srcs = ["ServerDownloadsBazeliskTest.kt"],
main_class = "org.jetbrains.bsp.bazel.ServerDownloadsBazeliskTest",
resources = ["//e2e/src/main/resources:bsp-e2e-resources"],
visibility = ["//e2e:__subpackages__"],
deps = [
"//commons",
"//e2e/src/main/kotlin/org/jetbrains/bsp/bazel/base",
"@maven//:ch_epfl_scala_bsp4j_2_13",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.jetbrains.bsp.bazel

import org.jetbrains.bsp.bazel.base.BazelBspTestBaseScenario
import org.jetbrains.bsp.bazel.base.BazelBspTestScenarioStep
import org.jetbrains.bsp.bazel.install.Install
import java.time.Duration

object ServerDownloadsBazeliskTest : BazelBspTestBaseScenario() {
@JvmStatic
fun main(args: Array<String>) = executeScenario()

override fun installServer() {
// DO NOT supply the -b flag to test whether bazelisk is downloaded
Install.main(
arrayOf(
"-d", workspaceDir,
"-t", "//...",
"--produce-trace-log"
)
)
}

override fun scenarioSteps(): List<BazelBspTestScenarioStep> = listOf(resolveProject())

private fun resolveProject(): BazelBspTestScenarioStep = BazelBspTestScenarioStep(
"resolve project"
) { testClient.testResolveProject(Duration.ofMinutes(2)) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import kotlin.system.exitProcess

abstract class BazelBspTestBaseScenario {

private val binary = System.getenv("BIT_BAZEL_BINARY")
private val workspaceDir = System.getenv("BIT_WORKSPACE_DIR")
protected val binary = System.getenv("BIT_BAZEL_BINARY")
protected val workspaceDir = System.getenv("BIT_WORKSPACE_DIR")

val targetPrefix = calculateTargetPrefix()
protected val testClient: BazelTestClient
Expand All @@ -30,7 +30,7 @@ abstract class BazelBspTestBaseScenario {
return if (majorVersion < 6) "" else "@"
}

private fun installServer() {
protected open fun installServer() {
Install.main(
arrayOf(
"-d", workspaceDir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ kt_jvm_library(
deps = [
"//executioncontext/api",
"//executioncontext/projectview:parser",
"@maven//:commons_io_commons_io",
"@maven//:org_apache_logging_log4j_log4j_api",
"@maven//:org_apache_logging_log4j_log4j_core",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,106 @@
package org.jetbrains.bsp.bazel.workspacecontext

import org.apache.logging.log4j.LogManager
import org.jetbrains.bsp.bazel.commons.FileUtils
import org.jetbrains.bsp.bazel.executioncontext.api.ExecutionContextSingletonEntity
import org.jetbrains.bsp.bazel.executioncontext.api.ExecutionContextEntityExtractor
import org.jetbrains.bsp.bazel.executioncontext.api.ExecutionContextEntityExtractorException
import org.jetbrains.bsp.bazel.projectview.model.ProjectView
import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewBazelBinarySection
import java.io.File
import java.net.URI
import java.nio.file.Path

data class BazelBinarySpec(
override val value: Path
override val value: Path,
) : ExecutionContextSingletonEntity<Path>()

private val log = LogManager.getLogger(BazelBinarySpec::class.java)

// TODO(abrams): update tests for the whole flow and mock different OSes
internal object BazelBinarySpecExtractor : ExecutionContextEntityExtractor<BazelBinarySpec> {

override fun fromProjectView(projectView: ProjectView): BazelBinarySpec =
when (projectView.bazelBinary) {
null -> findBazelOnPath()
else -> map(projectView.bazelBinary!!)
override fun fromProjectView(projectView: ProjectView): BazelBinarySpec {
val extracted = projectView.bazelBinary?.value
return if (extracted != null) {
BazelBinarySpec(extracted)
} else {
val path = findBazelOnPathOrNull() ?: downloadBazelisk()
?: throw ExecutionContextEntityExtractorException(
"bazel path",
"Could not find bazel on your PATH nor download bazelisk"
)
BazelBinarySpec(path)
}
}


private fun findBazelOnPath(): BazelBinarySpec =
findBazelOnPathOrNull()
?: throw
ExecutionContextEntityExtractorException(
"bazel path",
"Could not find bazel on your PATH"
)
private fun downloadBazelisk(): Path? {
log.info("Downloading bazelisk")
val downloadLink = calculateBazeliskDownloadLink()?.let {
try {
URI(it).toURL()
} catch (e: Exception) {
log.error("Could not parse bazelisk download link: $it")
return null
}
}
if (downloadLink == null) {
log.error("Could not calculate bazelisk download link (your OS should be one of: windows-amd64, linux-amd64, linux-arm64, darwin)")
return null
}
val cache = FileUtils.getCacheDirectory("bazelbsp")
if (cache == null) {
log.error("Could not find cache directory")
return null
}
// Download bazelisk to the cache folder
val bazeliskFile = File(cache, "bazelisk")
if (bazeliskFile.exists()) {
log.info("Bazelisk already exists in the cache folder: ${bazeliskFile.path}")
} else {
log.info("Downloading bazelisk to the cache folder: ${bazeliskFile.path}")
org.apache.commons.io.FileUtils.copyURLToFile(downloadLink,
bazeliskFile,
60 * 1000,
60 * 1000)
log.info("Downloaded bazelisk")
}
return bazeliskFile.toPath()
}

private fun findBazelOnPathOrNull(): BazelBinarySpec? =
private fun calculateBazeliskDownloadLink(): String? {
// TODO: https://youtrack.jetbrains.com/issue/BAZEL-743
val base = "https://github.com/bazelbuild/bazelisk/releases/download/v1.18.0/bazelisk-"
val os = System.getProperty("os.name").lowercase()
val arch = System.getProperty("os.arch").lowercase()
val suffix = when {
os.startsWith("windows") && arch == "amd64" -> base + "windows-amd64.exe"
os.startsWith("linux") && arch == "amd64" -> base + "linux-amd64"
os.startsWith("linux") && arch == "arm64" -> base + "linux-arm64"
os.startsWith("mac") -> base + "darwin"
else -> null
}
if (suffix == null) {
log.error("Could not calculate bazelisk download link (your OS should be one of: windows-amd64, linux-amd64, linux-arm64, darwin)")
}
return base + suffix
}

private fun findBazelOnPathOrNull(): Path? =
splitPath()
.map { mapToBazel(it) }
.firstOrNull { it.canExecute() }
?.toPath()
?.let { BazelBinarySpec(it) }
.flatMap { listOf(bazelFile(it, "bazel"), bazelFile(it, "bazelisk")) }
.firstOrNull()

private fun splitPath(): List<String> = System.getenv("PATH").split(File.pathSeparator)

private fun mapToBazel(path: String): File = File(path, calculateBazeliskExecName())

// TODO: update tests for the whole flow and mock different OSes
private fun calculateBazeliskExecName(): String {
val osName = System.getProperty("os.name").lowercase()
return with(osName) {
when {
startsWith("windows") -> "bazel.exe"
else -> "bazel"
}
}
private fun bazelFile(path: String, executable: String): Path? {
val file = File(path, calculateExecutableName(executable))
return if (file.exists() && file.canExecute()) file.toPath() else null
}

private fun map(bazelBinarySection: ProjectViewBazelBinarySection): BazelBinarySpec =
BazelBinarySpec(bazelBinarySection.value)
private fun calculateExecutableName(name: String): String = when {
System.getProperty("os.name").lowercase().startsWith("windows") -> "$name.exe"
else -> name
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class BazelBspServer(
bspClientTestNotifier,
bspState,
)
val serverLifetime = BazelBspServerLifetime()
val serverLifetime = BazelBspServerLifetime(workspaceContextProvider)
val bspRequestsRunner = BspRequestsRunner(serverLifetime)
return BazelServices(
serverLifetime,
Expand Down

This file was deleted.

Loading

0 comments on commit 89beaf7

Please sign in to comment.