diff --git a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BUILD b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BUILD index 7d62c161f..6895a39df 100644 --- a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BUILD +++ b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BUILD @@ -11,7 +11,10 @@ java_library( "//bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/utils", "//commons", "//logger", + "//server/src/main/java/org/jetbrains/bsp/bazel/server/bsp/info", "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_fasterxml_jackson_core_jackson_annotations", + "@maven//:com_fasterxml_jackson_core_jackson_databind", "@maven//:com_google_guava_guava", "@maven//:io_vavr_vavr", "@maven//:org_apache_logging_log4j_log4j_api", diff --git a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BasicBazelInfo.java b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BasicBazelInfo.java new file mode 100644 index 000000000..bb2987b1e --- /dev/null +++ b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BasicBazelInfo.java @@ -0,0 +1,41 @@ +package org.jetbrains.bsp.bazel.bazelrunner; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.nio.file.Path; +import java.util.Objects; + +public class BasicBazelInfo implements BazelInfo { + private final String execRoot; + private final Path workspaceRoot; + + public BasicBazelInfo( + @JsonProperty("execRoot") String execRoot, + @JsonProperty("workspaceRoot") Path workspaceRoot) { + this.execRoot = execRoot; + this.workspaceRoot = workspaceRoot; + } + + @Override + public String execRoot() { + return execRoot; + } + + @Override + public Path workspaceRoot() { + return workspaceRoot; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BasicBazelInfo that = (BasicBazelInfo) o; + return Objects.equals(execRoot, that.execRoot) + && Objects.equals(workspaceRoot, that.workspaceRoot); + } + + @Override + public int hashCode() { + return Objects.hash(execRoot, workspaceRoot); + } +} diff --git a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoResolver.java b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoResolver.java index 0122f68be..8cc6eddd8 100644 --- a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoResolver.java +++ b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoResolver.java @@ -4,36 +4,54 @@ import io.vavr.Tuple2; import io.vavr.collection.Map; import io.vavr.control.Option; +import java.nio.file.Paths; import java.util.function.Function; import java.util.regex.Pattern; public class BazelInfoResolver { private static final Pattern INFO_LINE_PATTERN = Pattern.compile("([\\w-]+): (.*)"); private final BazelRunner bazelRunner; + private final BazelInfoStorage storage; - public BazelInfoResolver(BazelRunner bazelRunner) { + public BazelInfoResolver(BazelRunner bazelRunner, BazelInfoStorage storage) { this.bazelRunner = bazelRunner; + this.storage = storage; } public BazelInfo resolveBazelInfo() { - return new LazyBazelInfo(Lazy.of(this::readBazelInfoMap)); + return new LazyBazelInfo(Lazy.of(() -> storage.load().getOrElse(this::bazelInfoFromBazel))); } - private Map readBazelInfoMap() { - var bazelProcessResult = + private BazelInfo bazelInfoFromBazel() { + var processResult = bazelRunner.commandBuilder().info().executeBazelCommand().waitAndGetResult(); + var info = parseBazelInfo(processResult); + storage.store(info); + return info; + } + + private BasicBazelInfo parseBazelInfo(BazelProcessResult bazelProcessResult) { + var outputMap = + bazelProcessResult + .stdoutLines() + .flatMap( + line -> { + var matcher = INFO_LINE_PATTERN.matcher(line); + return Option.when( + matcher.matches(), () -> new Tuple2<>(matcher.group(1), matcher.group(2))); + }) + .toMap(Function.identity()); + + var executionRoot = extract(outputMap, "execution_root"); + var workspace = Paths.get(extract(outputMap, "workspace")); + return new BasicBazelInfo(executionRoot, workspace); + } - return bazelProcessResult - .stdoutLines() - .flatMap( - line -> { - var matcher = INFO_LINE_PATTERN.matcher(line); - if (matcher.matches()) { - return Option.some(new Tuple2<>(matcher.group(1), matcher.group(2))); - } else { - return Option.none(); - } - }) - .toMap(Function.identity()); + private String extract(Map outputMap, String name) { + return outputMap + .get(name) + .getOrElseThrow( + () -> + new RuntimeException(String.format("Failed to resolve %s from bazel info", name))); } } diff --git a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoStorage.java b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoStorage.java new file mode 100644 index 000000000..e8e799874 --- /dev/null +++ b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelInfoStorage.java @@ -0,0 +1,52 @@ +package org.jetbrains.bsp.bazel.bazelrunner; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.vavr.control.Option; +import io.vavr.control.Try; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Function; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.bsp.bazel.server.bsp.info.BspInfo; + +public class BazelInfoStorage { + + private static final Logger LOGGER = LogManager.getLogger(BazelInfoStorage.class); + private final ObjectMapper mapper; + private final Path path; + + public BazelInfoStorage(BspInfo bspInfo) { + this(bspInfo.bazelBspDir().resolve("bazel-info-cache.json")); + } + + public BazelInfoStorage(Path path) { + this.path = path; + mapper = createMapper(); + } + + private ObjectMapper createMapper() { + var mapper = new ObjectMapper(); + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + return mapper; + } + + public Option load() { + return Option.when(Files.exists(path), this::read).flatMap(Function.identity()); + } + + private Option read() { + return Try.of(() -> mapper.readValue(path.toFile(), BasicBazelInfo.class)).toOption(); + } + + public void store(BasicBazelInfo bazelInfo) { + try { + mapper.writeValue(path.toFile(), bazelInfo); + } catch (IOException e) { + LOGGER.error("Could not store bazel info", e); + } + } +} diff --git a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelRunner.java b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelRunner.java index a6a8f68f1..990cfa5ba 100644 --- a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelRunner.java +++ b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/BazelRunner.java @@ -25,9 +25,8 @@ public class BazelRunner { // This is runner without workspace path. It is used to determine workspace // path and create a fully functional runner. - public static BazelRunner inCwd( - BazelPathProvider bazelPath, BspClientLogger bspClientLogger, List defaultFlags) { - return new BazelRunner(bazelPath, bspClientLogger, null, defaultFlags); + public static BazelRunner inCwd(BazelPathProvider bazelPath, BspClientLogger bspClientLogger) { + return new BazelRunner(bazelPath, bspClientLogger, null, List.of()); } public static BazelRunner of( diff --git a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/LazyBazelInfo.java b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/LazyBazelInfo.java index 90a897760..c303abdef 100644 --- a/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/LazyBazelInfo.java +++ b/bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner/LazyBazelInfo.java @@ -1,38 +1,22 @@ package org.jetbrains.bsp.bazel.bazelrunner; import io.vavr.Lazy; -import io.vavr.collection.Map; import java.nio.file.Path; -import java.nio.file.Paths; public class LazyBazelInfo implements BazelInfo { - private final Lazy> bazelInfoOutput; - private final Lazy execRoot; - private final Lazy workspaceRoot; + private final Lazy bazelInfo; - public LazyBazelInfo(Lazy> bazelInfoOutput) { - this.bazelInfoOutput = bazelInfoOutput; - this.execRoot = extract("execution_root"); - this.workspaceRoot = extract("workspace").map(Paths::get); + public LazyBazelInfo(Lazy bazelInfo) { + this.bazelInfo = bazelInfo; } @Override public String execRoot() { - return execRoot.get(); + return bazelInfo.get().execRoot(); } @Override public Path workspaceRoot() { - return workspaceRoot.get(); - } - - private Lazy extract(String name) { - return bazelInfoOutput.map( - map -> - map.get(name) - .getOrElseThrow( - () -> - new RuntimeException( - String.format("Failed to resolve %s from bazel info", name)))); + return bazelInfo.get().workspaceRoot(); } } diff --git a/bazelrunner/src/test/java/org/jetbrains/bsp/bazel/bazelrunner/BUILD b/bazelrunner/src/test/java/org/jetbrains/bsp/bazel/bazelrunner/BUILD new file mode 100644 index 000000000..47e43dd7c --- /dev/null +++ b/bazelrunner/src/test/java/org/jetbrains/bsp/bazel/bazelrunner/BUILD @@ -0,0 +1,12 @@ +load("//:junit5.bzl", "kt_junit5_test") + +kt_junit5_test( + name = "bazelrunner", + size = "small", + srcs = glob(["*.kt"]), + test_package = "org.jetbrains.bsp.bazel.bazelrunner", + deps = [ + "//bazelrunner/src/main/java/org/jetbrains/bsp/bazel/bazelrunner", + "@maven//:io_vavr_vavr", + ], +) diff --git a/bazelrunner/src/test/java/org/jetbrains/bsp/bazel/bazelrunner/StoredBazelInfoTest.kt b/bazelrunner/src/test/java/org/jetbrains/bsp/bazel/bazelrunner/StoredBazelInfoTest.kt new file mode 100644 index 000000000..92be6c561 --- /dev/null +++ b/bazelrunner/src/test/java/org/jetbrains/bsp/bazel/bazelrunner/StoredBazelInfoTest.kt @@ -0,0 +1,29 @@ +package org.jetbrains.bsp.bazel.bazelrunner; + +import org.assertj.core.api.Assertions +import org.assertj.core.api.AssertionsForClassTypes +import org.junit.jupiter.api.Test +import java.nio.file.Files +import java.nio.file.Paths + +class ProjectStorageTest { + @Test + fun shouldStoreAndLoadProject() { + val path = Paths.get(System.getProperty("java.io.tmpdir"), "bazel-info-cache-test.json") + .also { Files.deleteIfExists(it) } + + val storage = BazelInfoStorage(path) + + val empty = storage.load() + Assertions.assertThat(empty).isEmpty() + + val bazelInfo = BasicBazelInfo( + "/private/var/tmp/_bazel/125c7a6ca879ed16a4b4b1a74bc5f27b/execroot/bazel_bsp", + Paths.get("/Users/user/workspace/bazel-bsp")) + + storage.store(bazelInfo) + val loaded = storage.load() + loaded.forEach { Assertions.assertThat(it).isEqualTo(bazelInfo) } + loaded.onEmpty { AssertionsForClassTypes.fail("bazel info not loaded") } + } +} diff --git a/server/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java b/server/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java index 0b0c9105c..c6cc19b46 100644 --- a/server/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java +++ b/server/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java @@ -7,6 +7,7 @@ import org.eclipse.lsp4j.jsonrpc.Launcher; import org.jetbrains.bsp.bazel.bazelrunner.BazelInfo; import org.jetbrains.bsp.bazel.bazelrunner.BazelInfoResolver; +import org.jetbrains.bsp.bazel.bazelrunner.BazelInfoStorage; import org.jetbrains.bsp.bazel.bazelrunner.BazelRunner; import org.jetbrains.bsp.bazel.logger.BspClientLogger; import org.jetbrains.bsp.bazel.projectview.model.ProjectView; @@ -49,17 +50,16 @@ public class BazelBspServer { public BazelBspServer(BazelBspServerConfig config) { this.bspClientLogger = new BspClientLogger(); + var bspInfo = new BspInfo(); + var bazelInfoStorage = new BazelInfoStorage(bspInfo); var bazelDataResolver = - new BazelInfoResolver( - BazelRunner.inCwd( - config, bspClientLogger, getDefaultBazelFlags(config.currentProjectView()))); + new BazelInfoResolver(BazelRunner.inCwd(config, bspClientLogger), bazelInfoStorage); this.bazelInfo = bazelDataResolver.resolveBazelInfo(); this.bazelRunner = BazelRunner.of( config, bspClientLogger, bazelInfo, getDefaultBazelFlags(config.currentProjectView())); var serverLifetime = new BazelBspServerLifetime(); var bspRequestsRunner = new BspRequestsRunner(serverLifetime); - var bspInfo = new BspInfo(); this.compilationManager = new BazelBspCompilationManager(bazelRunner); var aspectsResolver = new InternalAspectsResolver(bazelInfo, bspInfo); var bazelBspAspectsManager = new BazelBspAspectsManager(compilationManager, aspectsResolver); diff --git a/server/src/test/java/org/jetbrains/bsp/bazel/server/bsp/utils/InternalAspectsResolverTest.java b/server/src/test/java/org/jetbrains/bsp/bazel/server/bsp/utils/InternalAspectsResolverTest.java index 435d545cc..bc87c547c 100644 --- a/server/src/test/java/org/jetbrains/bsp/bazel/server/bsp/utils/InternalAspectsResolverTest.java +++ b/server/src/test/java/org/jetbrains/bsp/bazel/server/bsp/utils/InternalAspectsResolverTest.java @@ -4,6 +4,7 @@ import java.nio.file.Path; import java.nio.file.Paths; +import org.jetbrains.bsp.bazel.bazelrunner.BasicBazelInfo; import org.jetbrains.bsp.bazel.bazelrunner.BazelInfo; import org.jetbrains.bsp.bazel.server.bsp.info.BspInfo; import org.junit.jupiter.api.Test; @@ -46,16 +47,6 @@ private InternalAspectsResolver createAspectsResolver( } private static BazelInfo createBazelInfo(Path workspaceRoot) { - return new BazelInfo() { - @Override - public String execRoot() { - return null; - } - - @Override - public Path workspaceRoot() { - return workspaceRoot; - } - }; + return new BasicBazelInfo(null, workspaceRoot); } } diff --git a/server/src/test/java/org/jetbrains/bsp/bazel/server/diagnostics/DiagnosticsServiceTest.kt b/server/src/test/java/org/jetbrains/bsp/bazel/server/diagnostics/DiagnosticsServiceTest.kt index 52dac6099..324a157f9 100644 --- a/server/src/test/java/org/jetbrains/bsp/bazel/server/diagnostics/DiagnosticsServiceTest.kt +++ b/server/src/test/java/org/jetbrains/bsp/bazel/server/diagnostics/DiagnosticsServiceTest.kt @@ -2,9 +2,8 @@ package org.jetbrains.bsp.bazel.server.diagnostics import ch.epfl.scala.bsp4j.* import org.assertj.core.api.Assertions -import org.jetbrains.bsp.bazel.bazelrunner.BazelInfo +import org.jetbrains.bsp.bazel.bazelrunner.BasicBazelInfo import org.junit.jupiter.api.Test -import java.nio.file.Path import java.nio.file.Paths import ch.epfl.scala.bsp4j.Diagnostic as BspDiagnostic import ch.epfl.scala.bsp4j.Position as BspPosition @@ -439,10 +438,7 @@ class DiagnosticsServiceTest { } private fun extractDiagnostics(output: String): List? { - val bazelInfo = object : BazelInfo { - override fun execRoot(): String? = null - override fun workspaceRoot(): Path = workspacePath - } + val bazelInfo = BasicBazelInfo(null, workspacePath) return DiagnosticsService(bazelInfo).extractDiagnostics(output) }