diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 000000000..afa9f536a --- /dev/null +++ b/.bazelignore @@ -0,0 +1 @@ +test-resources diff --git a/.bazelrc b/.bazelrc index 932c51cc1..235f9bfc3 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,2 +1,5 @@ build --java_toolchain=@bazel_tools//tools/jdk:toolchain_java8 -build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java8 \ No newline at end of file +build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java8 +build --javacopt="-Xep:WildcardImport:ERROR" +test --javacopt="-Xep:WildcardImport:ERROR" +run --javacopt="-Xep:WildcardImport:ERROR" diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 000000000..364ab5916 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,24 @@ +name: Format + +on: + pull_request: + branches: [ dev, master ] + +jobs: + google-java-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: "11" + - uses: axel-op/googlejavaformat-action@v3 + with: + skipCommit: true + - run: git diff --exit-code + buildifier: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: agluszak/bazel-buildifier-action@v0.3 + - run: git diff --exit-code diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 000000000..c8868165a --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,19 @@ +name: Integration test + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Run test + run: ./test.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..9c700af91 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,19 @@ +name: Unit tests + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Run tests + run: bazel test //... diff --git a/.gitignore b/.gitignore index 8c7c573da..39165ce99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,8 @@ -/.bazelbsp/bazelbsp.log -/.bazelbsp/bazelbsp.trace.json -/.bsp/bazelbsp.json +/.bazelbsp/ +/.bsp/ /.idea/ /.ijwb/ /bazel-* -/sample-repo/.bazelbsp/bazelbsp.trace.json -/sample-repo/.bazelbsp/bazelbsp.log -/sample-repo/bazel-* \ No newline at end of file +.DS_Store +test-resources/.bazelbsp/ +test-resources/.bsp/ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100755 index 000000000..7c5ce675e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,194 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + Copyright 2020-2021 JetBrains s.r.o. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README.md b/README.md index 70e550c76..806e8bd41 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ +[![JetBrains team project](http://jb.gg/badges/team.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) +[![Integration test](https://github.com/JetBrains/bazel-bsp/actions/workflows/integration-test.yml/badge.svg)](https://github.com/JetBrains/bazel-bsp/actions/workflows/integration-test.yml) + # Bazel BSP An implementation of the [Build Server Protocol](https://github.com/build-server-protocol/build-server-protocol) for Bazel. + ## Status Below is a list of languages supported over Bazel BSP and their implementation status. - Language: name of the Language @@ -10,23 +14,53 @@ Below is a list of languages supported over Bazel BSP and their implementation s - Test: Ability to test - Prerequisites: Any prerequisites needed to properly run the server to its full capabilities -| Language | Import | Compilation | Run | Test | Prerequisites | Notes | -| - | - | - | - | - | - | - | -| Scala | ✅ | ✅ | ❌ | ❌ | [Toolchain Registration](docs/scala.md) | N/A | -| Java | ✅ | ✅ | ❌ | ❌ | N/A | Compilation does not offer diagnostics | -| Kotlin | ✅ | ✅ | ❌ | ❌ | [Version](docs/kotlin.md) | KotlinJS support is minimal and not advised without further setting changes. Java source files in a kotlin rule will not possess diagnostics. | +| Language | Import | Compilation | Run | Test | Diagnostics | Prerequisites | Notes | +| - | - | - | - | - | - | - | - | +| Scala | ✅ | ✅ | ✅ | ✅ | ✅ | [Toolchain Registration](docs/scala.md) | N/A | +| Java | ✅ | ✅ | ✅ | ✅ | ❌ | N/A | N/A | +| Kotlin | ✅ | ✅ | ✅ | ✅ | ✅ | Requires [this version](https://github.com/agluszak/rules_kotlin/tree/diagnostics-updated) of rules_kotlin | KotlinJS support is minimal and not advised without further setting changes. Java source files in a kotlin rule will not possess diagnostics. | + ## Installation -1. Please make sure to have followed the prerequisites guide before proceeding. -2. Have [coursier](https://get-coursier.io/docs/cli-installation) installed -3. Run -`coursier launch -r sonatype:snapshots org.jetbrains.bsp:bazel-bsp:0.1.0-SNAPSHOT -M org.jetbrains.bsp.bazel.Install` -4. Add bsp generated folders to your `gitignore`: `.bsp` and `.bazelbsp` +### Easy way (coursier) +1. Have [coursier](https://get-coursier.io/docs/cli-installation) installed +2. Run in the directory where Bazel BSP should be installed: +``` +cs launch org.jetbrains.bsp:bazel-bsp:1.0.0-RC1 -M org.jetbrains.bsp.bazel.install.Install +``` +3. Add bsp generated folders to your `.gitignore`: `.bsp` and `.bazelbsp` + +### More difficult way (from sources) +Might be useful during development +#### Using install script +1. Be inside this project +2. Run `./install.sh` if you want to install Bazel BSP in this project or `./install.sh ` if you want to install it in a different directory + +#### Using coursier +1. Have [coursier](https://get-coursier.io/docs/cli-installation) installed +2. Be inside this project +3. Change project version - `maven_coordinates` attribute in the `src/main/java/org/jetbrains/bsp/bazel/BUILD` file +4. Publish a new version: +``` +bazel run --stamp --define "maven_repo=file://$HOME/.m2/repository" //src/main/java/org/jetbrains/bsp/bazel:bsp.publish +``` +7. Enter directory where Bazel BSP should be installed +8. Install your version: +``` +cs launch -r m2Local org.jetbrains.bsp:bazel-bsp: -M org.jetbrains.bsp.bazel.install.Install +``` + + +## Project Views +In order to work on huge monorepos you might want to specify directories and targets to work on. To address this issue, Bazel BSP supports part of the [Project Views](https://ij.bazel.build/docs/project-views.html) introduced by Google. Currently you can use following rules: `directories`, `targets` and `import`. + +Simply create a `projectview.bazelproject` file, specify rules inside and run the server. If no such files will be found, by default entire project will be loaded. + ## Extending In order to extend BSP server to other languages, make sure it can be supported with the current state of the [BSP Protocol](https://github.com/build-server-protocol/build-server-protocol/tree/master/docs). Also, make sure there's a [client](https://build-server-protocol.github.io/docs/implementations.html#build-clients), that will be able to support those changes. -The file `BazelBspServer.java`, holds the implementation of the server itself. For any JVM-language, the only needed changes would be for its specific Compiler Options Request (see, for example the [Scala Options Request](https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/extensions/scala.md#scalac-options-request)). If that language does not have its own Options Request, it may be possible to mimic the behavior with current Java or Scala specific requests. Furthermore, make sure the `FILE_EXTENSIONS` constant holds all the relevant extensions for the given language, as well as the `KNOWN_SOURCE_ROOTS` holds all known patterns for the given language, and, finally, the `SUPPORTED_LANGUAGES` should hold the LSP compliant name of the language. Anything else, should just work out of the box. +For any JVM-language, the only needed changes would be for its specific Compiler Options Request (see, for example the [Scala Options Request](https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/extensions/scala.md#scalac-options-request)). If that language does not have its own Options Request, it may be possible to mimic the behavior with current Java or Scala specific requests. Furthermore, make sure the `FILE_EXTENSIONS` constant holds all the relevant extensions for the given language, as well as the `KNOWN_SOURCE_ROOTS` holds all known patterns for the given language, and, finally, the `SUPPORTED_LANGUAGES` should hold the LSP compliant name of the language. Anything else, should just work out of the box. Any non-JVM language, will also need to look into the `buildTarget/dependencySources` request, since that request only searches for transitive dependencies in the form of jars. @@ -34,5 +68,6 @@ To support Compilation Diagnostics for the given language, they must be supporte Compilation Diagnostics should receive native support from bazel, accompany the state of that [here](https://github.com/bazelbuild/bazel/pull/11766). + ## Contributing This project follows [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). You can download a formatter plugin for Intellij [here](https://plugins.jetbrains.com/plugin/8527-google-java-format). diff --git a/WORKSPACE b/WORKSPACE index 0134b2893..01b0cd381 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,13 +2,15 @@ workspace(name = "bazel_bsp") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +# For maven: RULES_JVM_EXTERNAL_TAG = "4.0" + RULES_JVM_EXTERNAL_SHA = "31701ad93dbfe544d597dbe62c9a1fdd76d81d8a9150c2bf1ecf928ecdf97169" http_archive( name = "rules_jvm_external", - strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, sha256 = RULES_JVM_EXTERNAL_SHA, + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, ) @@ -105,9 +107,9 @@ py_repositories() http_archive( name = "googleapis", - sha256 = "a02d861fac93196fe020fd36ec2ad698d34e54c2394741be82b60c6c2334a4bf", - strip_prefix = "bazel-5ebe41f2900d71a99be08f7a675a79228506aec6/third_party/googleapis", - urls = ["https://github.com/andrefmrocha/bazel/archive/5ebe41f2900d71a99be08f7a675a79228506aec6.zip"], + sha256 = "4e5d2467df2994b13b2caaa0422985bedff804c3ae640fba23e63903172345ff", + strip_prefix = "bazel-caf13559e367da9c791cc5e559d2970400d5478b/third_party/googleapis", + urls = ["https://github.com/bazelbuild/bazel/archive/caf13559e367da9c791cc5e559d2970400d5478b.zip"], ) load("@io_bazel_rules_scala//scala:toolchains.bzl", "scala_register_toolchains") @@ -136,12 +138,8 @@ http_archive( url = "https://github.com/protocolbuffers/protobuf/archive/%s.tar.gz" % protobuf_version, ) -local_repository( - name = "sample_repo", - path = "sample-repo", -) - BAZEL_SONATYPE_TAG = "8c4bfd2a4c03c212446da134e0be3ab1ac605289" + http_archive( name = "bazel_sonatype", strip_prefix = "bazel-sonatype-%s" % BAZEL_SONATYPE_TAG, diff --git a/aspects.bzl b/aspects.bzl deleted file mode 100644 index e11d202bf..000000000 --- a/aspects.bzl +++ /dev/null @@ -1,30 +0,0 @@ -def _print_aspect_impl(target, ctx): - if hasattr(ctx.rule.attr, "srcjar"): - srcjar = ctx.rule.attr.srcjar - if srcjar != None: - for f in srcjar.files.to_list(): - if f != None: - print(f.path) - return [] - -print_aspect = aspect( - implementation = _print_aspect_impl, - attr_aspects = ["deps"], -) - -def _scala_compiler_classpath_impl(target, ctx): - files = depset() - if hasattr(ctx.rule.attr, "jars"): - for target in ctx.rule.attr.jars: - files = depset(transitive = [files, target.files]) - - compiler_classpath_file = ctx.actions.declare_file("%s.textproto" % target.label.name) - ctx.actions.write(compiler_classpath_file, struct(files = [file.path for file in files.to_list()]).to_proto()) - - return [ - OutputGroupInfo(scala_compiler_classpath_files = [compiler_classpath_file]), - ] - -scala_compiler_classpath_aspect = aspect( - implementation = _scala_compiler_classpath_impl, -) diff --git a/central-sync/BUILD b/central-sync/BUILD index 52a9f2fd1..ffb6efc54 100644 --- a/central-sync/BUILD +++ b/central-sync/BUILD @@ -1,4 +1,3 @@ exports_files([ "VERSION", ]) - diff --git a/central-sync/VERSION b/central-sync/VERSION index 4ecb66440..006eb744b 100644 --- a/central-sync/VERSION +++ b/central-sync/VERSION @@ -1 +1 @@ -0.1.0-SNAPSHOT \ No newline at end of file +1.0.0-RC1 \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 000000000..e92d7aaf0 --- /dev/null +++ b/install.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# script for building and running bazel bsp server locally +# script takes parameter with path to different project (as default this project) + +project_path=${1:-$PWD} + +echo -e "Building server..." +echo -e "============================" +bazel build //src/main/java/org/jetbrains/bsp/bazel:bsp-install +bsp_path="$(bazel info bazel-bin)/src/main/java/org/jetbrains/bsp/bazel/bsp-install" +echo -e "============================" +echo -e "Building done." + +echo -e "\nInstalling server at '$project_path' ..." +cd "$project_path" || { echo "cd $project_path failed! EXITING"; exit 155; } +$bsp_path + +echo -e "\nDone! Enjoy Bazel BSP!" diff --git a/main/src/org/jetbrains/bsp/bazel/BUILD b/main/src/org/jetbrains/bsp/bazel/BUILD deleted file mode 100644 index 638040032..000000000 --- a/main/src/org/jetbrains/bsp/bazel/BUILD +++ /dev/null @@ -1,54 +0,0 @@ -load("@bazel_sonatype//:defs.bzl", "sonatype_java_export") - - -sonatype_java_export( - name = "bsp", - srcs = glob(["*.java"]), - resources = ["aspects.bzl"], - maven_coordinates = "org.jetbrains.bsp:bazel-bsp:0.1.0", - maven_profile = "org.jetbrains", - visibility = ["//visibility:public"], - pom_template = "//:pom.xml", - deps = [ - "@com_google_protobuf//:protobuf_java", - "@googleapis//:google_devtools_build_v1_build_events_java_proto", - "@googleapis//:google_devtools_build_v1_publish_build_event_java_grpc", - "@googleapis//:google_devtools_build_v1_publish_build_event_java_proto", - "@googleapis//:google_devtools_build_v1_publish_build_event_proto", - "@io_bazel//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto", - "@io_bazel//src/main/protobuf:analysis_java_proto", - "@io_bazel//src/main/protobuf:build_java_proto", - "@io_bazel//third_party/grpc:grpc-jar", - "@io_bazel_rules_scala//src/protobuf/io/bazel/rules_scala:diagnostics_java_proto", - "@maven//:ch_epfl_scala_bsp4j", - "@maven//:com_google_code_gson_gson", - "@maven//:com_google_guava_guava", - "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", - "@maven//:org_eclipse_xtext_org_eclipse_xtext_xbase_lib", - ], -) - -java_binary( - name = "bsp-install", - srcs = glob(["*.java"]), - resources = ["aspects.bzl"], - main_class = "org.jetbrains.bsp.bazel.Install", - visibility = ["//visibility:public"], - deps = [ - "@com_google_protobuf//:protobuf_java", - "@googleapis//:google_devtools_build_v1_build_events_java_proto", - "@googleapis//:google_devtools_build_v1_publish_build_event_java_grpc", - "@googleapis//:google_devtools_build_v1_publish_build_event_java_proto", - "@googleapis//:google_devtools_build_v1_publish_build_event_proto", - "@io_bazel//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto", - "@io_bazel//src/main/protobuf:analysis_java_proto", - "@io_bazel//src/main/protobuf:build_java_proto", - "@io_bazel//third_party/grpc:grpc-jar", - "@io_bazel_rules_scala//src/protobuf/io/bazel/rules_scala:diagnostics_java_proto", - "@maven//:ch_epfl_scala_bsp4j", - "@maven//:com_google_code_gson_gson", - "@maven//:com_google_guava_guava", - "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", - "@maven//:org_eclipse_xtext_org_eclipse_xtext_xbase_lib", - ], -) diff --git a/main/src/org/jetbrains/bsp/bazel/BazelBspServer.java b/main/src/org/jetbrains/bsp/bazel/BazelBspServer.java deleted file mode 100644 index dd80d57c8..000000000 --- a/main/src/org/jetbrains/bsp/bazel/BazelBspServer.java +++ /dev/null @@ -1,1067 +0,0 @@ -package org.jetbrains.bsp.bazel; - -import ch.epfl.scala.bsp4j.BuildClient; -import ch.epfl.scala.bsp4j.BuildServer; -import ch.epfl.scala.bsp4j.BuildServerCapabilities; -import ch.epfl.scala.bsp4j.BuildTarget; -import ch.epfl.scala.bsp4j.BuildTargetCapabilities; -import ch.epfl.scala.bsp4j.BuildTargetDataKind; -import ch.epfl.scala.bsp4j.BuildTargetIdentifier; -import ch.epfl.scala.bsp4j.BuildTargetTag; -import ch.epfl.scala.bsp4j.CleanCacheParams; -import ch.epfl.scala.bsp4j.CleanCacheResult; -import ch.epfl.scala.bsp4j.CompileParams; -import ch.epfl.scala.bsp4j.CompileProvider; -import ch.epfl.scala.bsp4j.CompileResult; -import ch.epfl.scala.bsp4j.DependencySourcesItem; -import ch.epfl.scala.bsp4j.DependencySourcesParams; -import ch.epfl.scala.bsp4j.DependencySourcesResult; -import ch.epfl.scala.bsp4j.InitializeBuildParams; -import ch.epfl.scala.bsp4j.InitializeBuildResult; -import ch.epfl.scala.bsp4j.InverseSourcesParams; -import ch.epfl.scala.bsp4j.InverseSourcesResult; -import ch.epfl.scala.bsp4j.JavaBuildServer; -import ch.epfl.scala.bsp4j.JavacOptionsItem; -import ch.epfl.scala.bsp4j.JavacOptionsParams; -import ch.epfl.scala.bsp4j.JavacOptionsResult; -import ch.epfl.scala.bsp4j.JvmBuildTarget; -import ch.epfl.scala.bsp4j.LogMessageParams; -import ch.epfl.scala.bsp4j.MessageType; -import ch.epfl.scala.bsp4j.PublishDiagnosticsParams; -import ch.epfl.scala.bsp4j.ResourcesItem; -import ch.epfl.scala.bsp4j.ResourcesParams; -import ch.epfl.scala.bsp4j.ResourcesResult; -import ch.epfl.scala.bsp4j.RunParams; -import ch.epfl.scala.bsp4j.RunProvider; -import ch.epfl.scala.bsp4j.RunResult; -import ch.epfl.scala.bsp4j.ScalaBuildServer; -import ch.epfl.scala.bsp4j.ScalaBuildTarget; -import ch.epfl.scala.bsp4j.ScalaMainClassesParams; -import ch.epfl.scala.bsp4j.ScalaMainClassesResult; -import ch.epfl.scala.bsp4j.ScalaPlatform; -import ch.epfl.scala.bsp4j.ScalaTestClassesParams; -import ch.epfl.scala.bsp4j.ScalaTestClassesResult; -import ch.epfl.scala.bsp4j.ScalacOptionsItem; -import ch.epfl.scala.bsp4j.ScalacOptionsParams; -import ch.epfl.scala.bsp4j.ScalacOptionsResult; -import ch.epfl.scala.bsp4j.SourceItem; -import ch.epfl.scala.bsp4j.SourceItemKind; -import ch.epfl.scala.bsp4j.SourcesItem; -import ch.epfl.scala.bsp4j.SourcesParams; -import ch.epfl.scala.bsp4j.SourcesResult; -import ch.epfl.scala.bsp4j.StatusCode; -import ch.epfl.scala.bsp4j.TestParams; -import ch.epfl.scala.bsp4j.TestProvider; -import ch.epfl.scala.bsp4j.TestResult; -import ch.epfl.scala.bsp4j.WorkspaceBuildTargetsResult; -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.io.ByteStreams; -import com.google.devtools.build.lib.analysis.AnalysisProtos; -import com.google.devtools.build.lib.query2.proto.proto2api.Build; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; -import org.eclipse.lsp4j.jsonrpc.messages.Either; -import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; -import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode; - -public class BazelBspServer implements BuildServer, ScalaBuildServer, JavaBuildServer { - - public static final ImmutableSet KNOWN_SOURCE_ROOTS = - ImmutableSet.of("java", "scala", "kotlin", "javatests", "src", "test", "main", "testsrc"); - protected static final String SCALAC = "Scalac"; - protected static final String KOTLINC = "KotlinCompile"; - protected static final String JAVAC = "Javac"; - protected static final List SUPPORTED_LANGUAGES = - ImmutableList.of("scala", "java", "kotlin"); - private final String bazel; - private final String PUBLISH_ALL_ACTIONS = "--build_event_publish_all_actions"; - private final Map> targetsToSources = new HashMap<>(); - private final CompletableFuture isInitialized = new CompletableFuture<>(); - private final CompletableFuture isFinished = new CompletableFuture<>(); - private final List FILE_EXTENSIONS = - ImmutableList.of( - ".scala", ".java", ".kt", ".kts", ".sh", ".bzl", ".py", ".js", ".c", ".h", ".cpp", - ".hpp"); - public BepServer bepServer = null; - private String BES_BACKEND = "--bes_backend=grpc://localhost:"; - private CompletableFuture processLock = null; - private String execRoot = null; - private String workspaceRoot = null; - private String binRoot = null; - private String workspaceLabel = null; - private ScalaBuildTarget scalacClasspath = null; - private BuildClient buildClient; - - public BazelBspServer(String pathToBazel) { - this.bazel = pathToBazel; - } - - public void setBackendPort(int port) { - this.BES_BACKEND += port; - } - - private boolean isInitialized() { - try { - isInitialized.get(1, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - return false; - } - return true; - } - - private boolean isFinished() { - return isFinished.isDone(); - } - - private CompletableFuture completeExceptionally(ResponseError error) { - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(new ResponseErrorException(error)); - return future; - } - - private CompletableFuture handleBuildInitialize( - Supplier> request) { - if (isFinished()) - return completeExceptionally( - new ResponseError( - ResponseErrorCode.serverErrorEnd, "Server has already shutdown!", false)); - return getValue(request); - } - - private CompletableFuture handleBuildShutdown(Supplier> request) { - if (!isInitialized()) - return completeExceptionally( - new ResponseError( - ResponseErrorCode.serverErrorEnd, "Server has not been initialized yet!", false)); - return getValue(request); - } - - private CompletableFuture executeCommand(Supplier> request) { - if (!isInitialized()) - return completeExceptionally( - new ResponseError( - ResponseErrorCode.serverNotInitialized, - "Server has not been initialized yet!", - false)); - if (isFinished()) - return completeExceptionally( - new ResponseError( - ResponseErrorCode.serverErrorEnd, "Server has already shutdown!", false)); - - return getValue(request); - } - - private CompletableFuture getValue(Supplier> request) { - CompletableFuture> execution = CompletableFuture.supplyAsync(request); - Either either; - try { - either = execution.get(); - } catch (CompletionException | InterruptedException | ExecutionException e) { - e.printStackTrace(); - return completeExceptionally( - new ResponseError(ResponseErrorCode.InternalError, e.getMessage(), null)); - } - - if (either.isLeft()) return completeExceptionally(either.getLeft()); - else return CompletableFuture.completedFuture(either.getRight()); - } - - @Override - public CompletableFuture buildInitialize( - InitializeBuildParams initializeBuildParams) { - return handleBuildInitialize( - () -> { - BuildServerCapabilities capabilities = new BuildServerCapabilities(); - capabilities.setCompileProvider(new CompileProvider(SUPPORTED_LANGUAGES)); - capabilities.setRunProvider(new RunProvider(SUPPORTED_LANGUAGES)); - capabilities.setTestProvider(new TestProvider(SUPPORTED_LANGUAGES)); - capabilities.setDependencySourcesProvider(true); - capabilities.setInverseSourcesProvider(true); - capabilities.setResourcesProvider(true); - checkBazelInstallation(); - return Either.forRight( - new InitializeBuildResult( - Constants.NAME, Constants.VERSION, Constants.BSP_VERSION, capabilities)); - }); - } - - private void checkBazelInstallation() { - try { - Process process = startProcess(); - parseProcess(process); - // Force-populate cache to avoid deadlock when looking up information from BEP listener. - getExecRoot(); - getWorkspaceRoot(); - getBinRoot(); - getWorkspaceLabel(); - } catch (IOException | InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - - @Override - public void onBuildInitialized() { - isInitialized.complete(null); - } - - @Override - public CompletableFuture buildShutdown() { - return handleBuildShutdown( - () -> { - isFinished.complete(null); - return Either.forRight(new Object()); - }); - } - - @Override - public void onBuildExit() { - try { - isFinished.get(1, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - System.exit(1); - } - - System.exit(0); - } - - @Override - public CompletableFuture workspaceBuildTargets() { - // TODO(illicitonion): Parameterise this to allow importing a subset of //... - return executeCommand( - () -> { - try { - Build.QueryResult queryResult = - getQuery( - "query", - "--output=proto", - "--nohost_deps", - "--noimplicit_deps", - "kind(binary, //...) union kind(library, //...) union kind(test, //...)"); - List targets = - queryResult.getTargetList().stream() - .map(Build.Target::getRule) - .filter(rule -> !rule.getRuleClass().equals("filegroup")) - .map(this::getBuildTarget) - .collect(Collectors.toList()); - return Either.forRight(new WorkspaceBuildTargetsResult(targets)); - } catch (IOException e) { - return Either.forLeft( - new ResponseError(ResponseErrorCode.InternalError, e.getMessage(), null)); - } - }); - } - - private Build.QueryResult getQuery(String... args) throws IOException { - try { - Process process = startProcess(args); - - Build.QueryResult queryResult = Build.QueryResult.parseFrom(process.getInputStream()); - processLock.complete(null); - return queryResult; - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - - private BuildTarget getBuildTarget(Build.Rule rule) { - String name = rule.getName(); - System.out.println("Getting targets for rule: " + name); - List deps = - rule.getAttributeList().stream() - .filter(attribute -> attribute.getName().equals("deps")) - .flatMap(srcDeps -> srcDeps.getStringListValueList().stream()) - .map(BuildTargetIdentifier::new) - .collect(Collectors.toList()); - BuildTargetIdentifier label = new BuildTargetIdentifier(name); - - List sources = getSourceItems(rule, label); - - Set extensions = new TreeSet<>(); - - for (SourceItem source : sources) { - if (source.getUri().endsWith(".scala")) { - extensions.add("scala"); - } else if (source.getUri().endsWith(".java")) { - extensions.add("java"); - } else if (source.getUri().endsWith(".kt")) { - extensions.add("kotlin"); - extensions.add("java"); // TODO(andrefmrocha): Remove this when kotlin is natively supported - } - } - - String ruleClass = rule.getRuleClass(); - BuildTarget target = - new BuildTarget( - label, - new ArrayList<>(), - new ArrayList<>(extensions), - deps, - new BuildTargetCapabilities( - true, ruleClass.endsWith("_test"), ruleClass.endsWith("_binary"))); - target.setBaseDirectory(Uri.packageDirFromLabel(label.getUri(), getWorkspaceRoot()).toString()); - target.setDisplayName(label.getUri()); - if (extensions.contains("scala")) { - getScalaBuildTarget() - .ifPresent( - (buildTarget) -> { - target.setDataKind(BuildTargetDataKind.SCALA); - target.setTags(Lists.newArrayList(getRuleType(rule))); - target.setData(buildTarget); - }); - } else if (extensions.contains("java") || extensions.contains("kotlin")) { - target.setDataKind(BuildTargetDataKind.JVM); - target.setTags(Lists.newArrayList(getRuleType(rule))); - target.setData(getJVMBuildTarget()); - } - return target; - } - - private String getRuleType(Build.Rule rule) { - String ruleClass = rule.getRuleClass(); - if (ruleClass.contains("library")) return BuildTargetTag.LIBRARY; - if (ruleClass.contains("binary")) return BuildTargetTag.APPLICATION; - if (ruleClass.contains("test")) return BuildTargetTag.TEST; - return BuildTargetTag.NO_IDE; - } - - private List getSourceItems(Build.Rule rule, BuildTargetIdentifier label) { - List srcs = getSrcs(rule, false); - srcs.addAll(getSrcs(rule, true)); - targetsToSources.put(label, srcs); - return srcs; - } - - private List getSrcs(Build.Rule rule, boolean isGenerated) { - String srcType = isGenerated ? "generated_srcs" : "srcs"; - return getSrcsPaths(rule, srcType).stream() - .map(uri -> new SourceItem(uri.toString(), SourceItemKind.FILE, isGenerated)) - .collect(Collectors.toList()); - } - - private List getSrcsPaths(Build.Rule rule, String srcType) { - return rule.getAttributeList().stream() - .filter(attribute -> attribute.getName().equals(srcType)) - .flatMap(srcsSrc -> srcsSrc.getStringListValueList().stream()) - .flatMap( - dep -> { - if (isSourceFile(dep)) - return Lists.newArrayList(Uri.fromFileLabel(dep, getWorkspaceRoot())).stream(); - - try { - Build.QueryResult queryResult = getQuery("query", "--output=proto", dep); - return queryResult.getTargetList().stream() - .map(Build.Target::getRule) - .flatMap(queryRule -> getSrcsPaths(queryRule, srcType).stream()) - .collect(Collectors.toList()) - .stream(); - } catch (IOException e) { - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - private boolean isSourceFile(String dep) { - return FILE_EXTENSIONS.stream().anyMatch(dep::endsWith) && !dep.startsWith("@"); - } - - private Optional getScalaBuildTarget() { - if (scalacClasspath == null) { - buildTargetsWithBep( - Lists.newArrayList( - new BuildTargetIdentifier( - "@io_bazel_rules_scala_scala_library//:io_bazel_rules_scala_scala_library"), - new BuildTargetIdentifier( - "@io_bazel_rules_scala_scala_reflect//:io_bazel_rules_scala_scala_reflect"), - new BuildTargetIdentifier( - "@io_bazel_rules_scala_scala_compiler//:io_bazel_rules_scala_scala_compiler")), - Lists.newArrayList( - "--aspects=@//.bazelbsp:aspects.bzl%scala_compiler_classpath_aspect", - "--output_groups=scala_compiler_classpath_files")); - List classpath = - bepServer.fetchScalacClasspath().stream().map(Uri::toString).collect(Collectors.toList()); - List scalaVersions = - classpath.stream() - .filter(uri -> uri.contains("scala-library")) - .collect(Collectors.toList()); - if (scalaVersions.size() != 1) return Optional.empty(); - String scalaVersion = - scalaVersions - .get(0) - .substring( - scalaVersions.get(0).indexOf("scala-library-") + 14, - scalaVersions.get(0).indexOf(".jar")); - scalacClasspath = - new ScalaBuildTarget( - "org.scala-lang", - scalaVersion, - scalaVersion.substring(0, scalaVersion.lastIndexOf(".")), - ScalaPlatform.JVM, - classpath); - scalacClasspath.setJvmBuildTarget(getJVMBuildTarget()); - } - return Optional.of(scalacClasspath); - } - - private JvmBuildTarget getJVMBuildTarget() { - // TODO(andrefmrocha): Properly determine jdk path - return new JvmBuildTarget(null, getJavaVersion()); - } - - private String getJavaVersion() { - String version = System.getProperty("java.version"); - if (version.startsWith("1.")) { - version = version.substring(0, 3); - } else { - int dot = version.indexOf("."); - if (dot != -1) { - version = version.substring(0, dot); - } - } - return version; - } - - private List runBazelLines(String... args) { - List lines = - Splitter.on("\n") - .omitEmptyStrings() - .splitToList(new String(runBazelBytes(args), StandardCharsets.UTF_8)); - System.out.printf("Returning: %s%n", lines); - return lines; - } - - private byte[] runBazelBytes(String... args) { - try { - Process process = startProcess(args); - byte[] byteArray = ByteStreams.toByteArray(process.getInputStream()); - processLock.complete(null); - return byteArray; - } catch (IOException | InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - - private synchronized Process startProcess(String... args) - throws IOException, InterruptedException, ExecutionException { - List argv = new ArrayList<>(args.length + 3); - argv.add(bazel); - argv.addAll(Arrays.asList(args)); - if (argv.size() > 1) { - argv.add(2, BES_BACKEND); - argv.add(3, PUBLISH_ALL_ACTIONS); - } - - if (processLock != null) processLock.get(); - System.out.printf("Running: %s%n", argv); - processLock = new CompletableFuture<>(); - return new ProcessBuilder(argv).start(); - } - - private int parseProcess(Process process) throws IOException, InterruptedException { - Set messageBuilder = new HashSet<>(); - String line; - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); - while ((line = reader.readLine()) != null) messageBuilder.add(line.trim()); - - String message = String.join("\n", messageBuilder); - int returnCode = process.waitFor(); - if (returnCode != 0) logError(message); - else logMessage(message); - processLock.complete(null); - return returnCode; - } - - protected void logError(String errorMessage) { - LogMessageParams params = new LogMessageParams(MessageType.ERROR, errorMessage); - buildClient.onBuildLogMessage(params); - throw new RuntimeException(errorMessage); - } - - protected void logMessage(String message) { - LogMessageParams params = new LogMessageParams(MessageType.LOG, message); - buildClient.onBuildLogMessage(params); - } - - @Override - public CompletableFuture buildTargetSources(SourcesParams sourcesParams) { - return executeCommand( - () -> { - try { - Build.QueryResult queryResult = - getQuery( - "query", - "--output=proto", - "(" - + sourcesParams.getTargets().stream() - .map(BuildTargetIdentifier::getUri) - .collect(Collectors.joining("+")) - + ")"); - - List sources = - queryResult.getTargetList().stream() - .map(Build.Target::getRule) - .map( - rule -> { - BuildTargetIdentifier label = new BuildTargetIdentifier(rule.getName()); - List items = this.getSourceItems(rule, label); - List roots = - Lists.newArrayList( - Uri.fromAbsolutePath(getSourcesRoot(rule.getName())).toString()); - SourcesItem item = new SourcesItem(label, items); - item.setRoots(roots); - return item; - }) - .collect(Collectors.toList()); - return Either.forRight(new SourcesResult(sources)); - } catch (IOException e) { - return Either.forLeft( - new ResponseError(ResponseErrorCode.InternalError, e.getMessage(), null)); - } - }); - } - - private String getSourcesRoot(String uri) { - List root = - KNOWN_SOURCE_ROOTS.stream().filter(uri::contains).collect(Collectors.toList()); - return getWorkspaceRoot() - + (root.size() == 0 - ? "" - : uri.substring(1, uri.indexOf(root.get(0)) + root.get(0).length())); - } - - public synchronized String getWorkspaceRoot() { - if (workspaceRoot == null) { - workspaceRoot = Iterables.getOnlyElement(runBazelLines("info", "workspace")); - } - return workspaceRoot; - } - - public synchronized String getBinRoot() { - if (binRoot == null) { - binRoot = Iterables.getOnlyElement(runBazelLines("info", "bazel-bin")); - } - return binRoot; - } - - public synchronized String getExecRoot() { - if (execRoot == null) { - execRoot = Iterables.getOnlyElement(runBazelLines("info", "execution_root")); - } - return execRoot; - } - - public synchronized String getWorkspaceLabel() { - if (workspaceLabel == null) { - Path workspacePath = Paths.get(getExecRoot()); - workspaceLabel = workspacePath.toFile().getName(); - } - return workspaceLabel; - } - - @Override - public CompletableFuture buildTargetInverseSources( - InverseSourcesParams inverseSourcesParams) { - return executeCommand( - () -> { - String fileUri = inverseSourcesParams.getTextDocument().getUri(); - String workspaceRoot = getWorkspaceRoot(); - String prefix = Uri.fromWorkspacePath("", workspaceRoot).toString(); - if (!inverseSourcesParams.getTextDocument().getUri().startsWith(prefix)) { - throw new RuntimeException( - "Could not resolve " + fileUri + " within workspace " + prefix); - } - try { - Build.QueryResult result = - getQuery( - "query", - "--output=proto", - "kind(rule, rdeps(//..., " + fileUri.substring(prefix.length()) + ", 1))"); - List targets = - result.getTargetList().stream() - .map(Build.Target::getRule) - .map(Build.Rule::getName) - .map(BuildTargetIdentifier::new) - .collect(Collectors.toList()); - - return Either.forRight(new InverseSourcesResult(targets)); - } catch (IOException e) { - return Either.forLeft( - new ResponseError(ResponseErrorCode.InternalError, e.getMessage(), null)); - } - }); - } - - @Override - public CompletableFuture buildTargetDependencySources( - DependencySourcesParams dependencySourcesParams) { - return executeCommand( - () -> { - List targets = - dependencySourcesParams.getTargets().stream() - .map(BuildTargetIdentifier::getUri) - .collect(Collectors.toList()); - - DependencySourcesResult result = - new DependencySourcesResult( - targets.stream() - .sorted() - .map( - target -> { - List files = - lookupTransitiveSourceJars(target).stream() - .map( - execPath -> - Uri.fromExecPath(execPath, getExecRoot()).toString()) - .collect(Collectors.toList()); - return new DependencySourcesItem( - new BuildTargetIdentifier(target), files); - }) - .collect(Collectors.toList())); - return Either.forRight(result); - }); - } - - private List runBazelStderr(String... args) { - try { - Process process = startProcess(args); - List output = new ArrayList<>(); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); - String line; - while ((line = reader.readLine()) != null) { - output.add(line.trim()); - } - processLock.complete(null); - return output; - } catch (IOException | InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - - private List lookupTransitiveSourceJars(String target) { - // TODO(illicitonion): Use an aspect output group, rather than parsing stderr logging - List lines = - runBazelStderr("build", "--aspects", "@//.bazelbsp:aspects.bzl%print_aspect", target); - return lines.stream() - .map(line -> Splitter.on(" ").splitToList(line)) - .filter( - parts -> - parts.size() == 3 - && parts.get(0).equals("DEBUG:") - && parts.get(1).contains(".bazelbsp/aspects.bzl") - && parts.get(2).endsWith(".jar")) - .map(parts -> "exec-root://" + parts.get(2)) - .collect(Collectors.toList()); - } - - @Override - public CompletableFuture buildTargetResources(ResourcesParams resourcesParams) { - return executeCommand( - () -> { - try { - Build.QueryResult query = getQuery("query", "--output=proto", "//..."); - System.out.println("Resources query result " + query); - ResourcesResult resourcesResult = - new ResourcesResult( - query.getTargetList().stream() - .map(Build.Target::getRule) - .filter( - rule -> - resourcesParams.getTargets().stream() - .anyMatch(target -> target.getUri().equals(rule.getName()))) - .filter( - rule -> - rule.getAttributeList().stream() - .anyMatch( - attribute -> - attribute.getName().equals("resources") - && attribute.hasExplicitlySpecified() - && attribute.getExplicitlySpecified())) - .map( - rule -> - new ResourcesItem( - new BuildTargetIdentifier(rule.getName()), - getResources(rule, query))) - .collect(Collectors.toList())); - return Either.forRight(resourcesResult); - } catch (IOException e) { - return Either.forLeft( - new ResponseError(ResponseErrorCode.InternalError, e.getMessage(), null)); - } - }); - } - - private List getResources(Build.Rule rule, Build.QueryResult queryResult) { - return rule.getAttributeList().stream() - .filter( - attribute -> - attribute.getName().equals("resources") - && attribute.hasExplicitlySpecified() - && attribute.getExplicitlySpecified()) - .flatMap( - attribute -> { - List targetsRule = - attribute.getStringListValueList().stream() - .map(label -> isPackage(queryResult, label)) - .filter(targets -> !targets.isEmpty()) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - List targetsResources = getResourcesOutOfRule(targetsRule); - - List resources = - attribute.getStringListValueList().stream() - .filter(label -> isPackage(queryResult, label).isEmpty()) - .map(label -> Uri.fromFileLabel(label, getWorkspaceRoot()).toString()) - .collect(Collectors.toList()); - - return Stream.concat(targetsResources.stream(), resources.stream()); - }) - .collect(Collectors.toList()); - } - - private List isPackage(Build.QueryResult queryResult, String label) { - return queryResult.getTargetList().stream() - .filter(target -> target.hasRule() && target.getRule().getName().equals(label)) - .collect(Collectors.toList()); - } - - private List getResourcesOutOfRule(List rules) { - return rules.stream() - .flatMap(resourceRule -> resourceRule.getRule().getAttributeList().stream()) - .filter((srcAttribute) -> srcAttribute.getName().equals("srcs")) - .flatMap(resourceAttribute -> resourceAttribute.getStringListValueList().stream()) - .map(src -> Uri.fromFileLabel(src, getWorkspaceRoot()).toString()) - .collect(Collectors.toList()); - } - - @Override - public CompletableFuture buildTargetCompile(CompileParams compileParams) { - return executeCommand(() -> buildTargetsWithBep(compileParams.getTargets(), new ArrayList<>())); - } - - private Either buildTargetsWithBep( - List targets, List extraFlags) { - List args = Lists.newArrayList(bazel, "build", BES_BACKEND, PUBLISH_ALL_ACTIONS); - args.addAll(targets.stream().map(BuildTargetIdentifier::getUri).collect(Collectors.toList())); - args.addAll(extraFlags); - int exitCode = -1; - - final Map diagnosticsProtosLocations = - bepServer.getDiagnosticsProtosLocations(); - try { - Build.QueryResult queryResult = - getQuery( - "query", - "--output=proto", - "(" - + targets.stream() - .map(BuildTargetIdentifier::getUri) - .collect(Collectors.joining("+")) - + ")"); - - for (Build.Target target : queryResult.getTargetList()) { - target.getRule().getRuleOutputList().stream() - .filter(output -> output.contains("diagnostics")) - .forEach( - output -> - diagnosticsProtosLocations.put( - target.getRule().getName(), convertOutputToPath(output, getBinRoot()))); - } - } catch (IOException e) { - e.printStackTrace(); - } - - try { - if (targetsToSources.isEmpty()) workspaceBuildTargets().wait(); - - Process process = new ProcessBuilder(args).start(); - exitCode = parseProcess(process); - } catch (InterruptedException | IOException e) { - System.out.println("Failed to run bazel: " + e); - } - - for (Map.Entry diagnostics : diagnosticsProtosLocations.entrySet()) { - String target = diagnostics.getKey(); - String diagnosticsPath = diagnostics.getValue(); - Map> filesToDiagnostics = new HashMap<>(); - try { - BuildTargetIdentifier targetIdentifier = new BuildTargetIdentifier(target); - bepServer.getDiagnostics(filesToDiagnostics, targetIdentifier, diagnosticsPath); - bepServer.emitDiagnostics(filesToDiagnostics, targetIdentifier); - } catch (IOException e) { - System.err.println("Failed to get diagnostics for " + target); - } - } - - return Either.forRight(new CompileResult(BepServer.convertExitCode(exitCode))); - } - - private String convertOutputToPath(String output, String prefix) { - String pathToFile = output.replaceAll("(//|:)", "/"); - return prefix + pathToFile; - } - - @Override - public CompletableFuture buildTargetTest(TestParams testParams) { - return executeCommand( - () -> { - Either build = - buildTargetsWithBep(Lists.newArrayList(testParams.getTargets()), new ArrayList<>()); - if (build.isLeft()) return Either.forLeft(build.getLeft()); - - CompileResult result = build.getRight(); - if (result.getStatusCode() != StatusCode.OK) - return Either.forRight(new TestResult(result.getStatusCode())); - - try { - Process process = - startProcess( - Lists.asList( - "test", - "(" - + Joiner.on("+") - .join( - testParams.getTargets().stream() - .map(BuildTargetIdentifier::getUri) - .collect(Collectors.toList())) - + ")", - testParams.getArguments().toArray(new String[0])) - .toArray(new String[0])); - - int returnCode = parseProcess(process); - return Either.forRight(new TestResult(BepServer.convertExitCode(returnCode))); - } catch (IOException | InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - }); - } - - @Override - public CompletableFuture buildTargetRun(RunParams runParams) { - return executeCommand( - () -> { - Either build = - buildTargetsWithBep(Lists.newArrayList(runParams.getTarget()), new ArrayList<>()); - if (build.isLeft()) return Either.forLeft(build.getLeft()); - - CompileResult result = build.getRight(); - if (result.getStatusCode() != StatusCode.OK) - return Either.forRight(new RunResult(result.getStatusCode())); - - try { - Process process = - startProcess( - Lists.asList( - "run", - runParams.getTarget().getUri(), - runParams.getArguments().toArray(new String[0])) - .toArray(new String[0])); - - int returnCode = parseProcess(process); - return Either.forRight(new RunResult(BepServer.convertExitCode(returnCode))); - } catch (IOException | InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - }); - } - - @Override - public CompletableFuture buildTargetCleanCache( - CleanCacheParams cleanCacheParams) { - return executeCommand( - () -> { - CleanCacheResult result; - try { - result = new CleanCacheResult(String.join("\n", runBazelLines("clean")), true); - } catch (RuntimeException e) { - result = new CleanCacheResult(e.getMessage(), false); - } - return Either.forRight(result); - }); - } - - @Override - public CompletableFuture buildTargetScalacOptions( - ScalacOptionsParams scalacOptionsParams) { - return executeCommand( - () -> { - List targets = - scalacOptionsParams.getTargets().stream() - .map(BuildTargetIdentifier::getUri) - .collect(Collectors.toList()); - String targetsUnion = Joiner.on(" + ").join(targets); - Map> targetsOptions = getTargetsOptions(targetsUnion, "scalacopts"); - Either either = - parseActionGraph(getMnemonics(targetsUnion, Lists.newArrayList(SCALAC, JAVAC))); - if (either.isLeft()) return Either.forLeft(either.getLeft()); - - ScalacOptionsResult result = - new ScalacOptionsResult( - targets.stream() - .flatMap( - target -> - collectScalacOptionsResult( - either.getRight(), - targetsOptions.getOrDefault(target, new ArrayList<>()), - either.getRight().getInputsAsUri(target, getExecRoot()), - target)) - .collect(Collectors.toList())); - return Either.forRight(result); - }); - } - - @Override - public CompletableFuture buildTargetJavacOptions( - JavacOptionsParams javacOptionsParams) { - return executeCommand( - () -> { - List targets = - javacOptionsParams.getTargets().stream() - .map(BuildTargetIdentifier::getUri) - .collect(Collectors.toList()); - - String targetsUnion = Joiner.on(" + ").join(targets); - Map> targetsOptions = getTargetsOptions(targetsUnion, "javacopts"); - // TODO(andrefmrocha): Remove this when kotlin is natively supported - Either either = - parseActionGraph(getMnemonics(targetsUnion, Lists.newArrayList(JAVAC, KOTLINC))); - if (either.isLeft()) return Either.forLeft(either.getLeft()); - - JavacOptionsResult result = - new JavacOptionsResult( - targets.stream() - .flatMap( - target -> - collectJavacOptionsResult( - either.getRight(), - targetsOptions.getOrDefault(target, new ArrayList<>()), - either.getRight().getInputsAsUri(target, getExecRoot()), - target)) - .collect(Collectors.toList())); - return Either.forRight(result); - }); - } - - private Map> getTargetsOptions( - String targetsUnion, String compilerOptionsName) { - try { - Build.QueryResult query = getQuery("query", "--output=proto", "(" + targetsUnion + ")"); - - return query.getTargetList().stream() - .map(Build.Target::getRule) - .collect( - Collectors.toMap( - Build.Rule::getName, - (rule) -> - rule.getAttributeList().stream() - .filter(attr -> attr.getName().equals(compilerOptionsName)) - .flatMap(attr -> attr.getStringListValueList().stream()) - .collect(Collectors.toList()))); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private Stream collectJavacOptionsResult( - ActionGraphParser actionGraphParser, - List options, - List inputs, - String target) { - return actionGraphParser.getOutputs(target, Lists.newArrayList(".jar", ".js")).stream() - .map( - output -> - new JavacOptionsItem( - new BuildTargetIdentifier(target), - options, - inputs, - Uri.fromExecPath("exec-root://" + output, execRoot).toString())); - } - - private Either parseActionGraph(String query) { - try { - AnalysisProtos.ActionGraphContainer actionGraph = - AnalysisProtos.ActionGraphContainer.parseFrom( - runBazelBytes("aquery", "--output=proto", query)); - return Either.forRight(new ActionGraphParser(actionGraph)); - } catch (IOException e) { - return Either.forLeft( - new ResponseError(ResponseErrorCode.InternalError, e.getMessage(), null)); - } - } - - private String getMnemonics(String targetsUnion, List languageIds) { - return languageIds.stream() - .filter(Objects::nonNull) - .map(mnemonic -> "mnemonic(" + mnemonic + ", " + targetsUnion + ")") - .collect(Collectors.joining(" union ")); - } - - private Stream collectScalacOptionsResult( - ActionGraphParser actionGraphParser, - List options, - List inputs, - String target) { - List suffixes = Lists.newArrayList(".jar", ".js"); - return actionGraphParser.getOutputs(target, suffixes).stream() - .map( - output -> - new ScalacOptionsItem( - new BuildTargetIdentifier(target), - options, - inputs, - Uri.fromExecPath("exec-root://" + output, execRoot).toString())); - } - - @Override - public CompletableFuture buildTargetScalaTestClasses( - ScalaTestClassesParams scalaTestClassesParams) { - System.out.printf("DWH: Got buildTargetScalaTestClasses: %s%n", scalaTestClassesParams); - // TODO(illicitonion): Populate - return CompletableFuture.completedFuture(new ScalaTestClassesResult(new ArrayList<>())); - } - - @Override - public CompletableFuture buildTargetScalaMainClasses( - ScalaMainClassesParams scalaMainClassesParams) { - System.out.printf("DWH: Got buildTargetScalaMainClasses: %s%n", scalaMainClassesParams); - // TODO(illicitonion): Populate - return CompletableFuture.completedFuture(new ScalaMainClassesResult(new ArrayList<>())); - } - - public Iterable getCachedBuildTargetSources(BuildTargetIdentifier target) { - return targetsToSources.getOrDefault(target, new ArrayList<>()); - } - - public void setBuildClient(BuildClient buildClient) { - this.buildClient = buildClient; - } -} diff --git a/main/src/org/jetbrains/bsp/bazel/BepServer.java b/main/src/org/jetbrains/bsp/bazel/BepServer.java deleted file mode 100644 index 569465ba5..000000000 --- a/main/src/org/jetbrains/bsp/bazel/BepServer.java +++ /dev/null @@ -1,385 +0,0 @@ -package org.jetbrains.bsp.bazel; - -import ch.epfl.scala.bsp4j.BuildClient; -import ch.epfl.scala.bsp4j.BuildTargetIdentifier; -import ch.epfl.scala.bsp4j.Diagnostic; -import ch.epfl.scala.bsp4j.DiagnosticSeverity; -import ch.epfl.scala.bsp4j.Position; -import ch.epfl.scala.bsp4j.PublishDiagnosticsParams; -import ch.epfl.scala.bsp4j.Range; -import ch.epfl.scala.bsp4j.SourceItem; -import ch.epfl.scala.bsp4j.StatusCode; -import ch.epfl.scala.bsp4j.TaskFinishParams; -import ch.epfl.scala.bsp4j.TaskId; -import ch.epfl.scala.bsp4j.TaskStartParams; -import ch.epfl.scala.bsp4j.TextDocumentIdentifier; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; -import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; -import com.google.devtools.build.v1.BuildEvent; -import com.google.devtools.build.v1.PublishBuildEventGrpc; -import com.google.devtools.build.v1.PublishBuildToolEventStreamRequest; -import com.google.devtools.build.v1.PublishBuildToolEventStreamResponse; -import com.google.devtools.build.v1.PublishLifecycleEventRequest; -import com.google.protobuf.Empty; -import io.bazel.rules_scala.diagnostics.Diagnostics; -import io.grpc.stub.StreamObserver; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Stack; -import java.util.TreeSet; - -public class BepServer extends PublishBuildEventGrpc.PublishBuildEventImplBase { - - private static final Set SUPPORTED_ACTIONS = - ImmutableSet.of(BazelBspServer.KOTLINC, BazelBspServer.JAVAC, BazelBspServer.SCALAC); - private final BazelBspServer bspServer; - private final BuildClient bspClient; - private final Map namedSetsOfFiles = - new HashMap<>(); - private final TreeSet compilerClasspathTextProtos = new TreeSet<>(); - private final TreeSet compilerClasspath = new TreeSet<>(); - private final Stack taskParkingLot = new Stack<>(); - private final String workspace = "WORKSPACE"; - private final String build = "BUILD"; - private Map diagnosticsProtosLocations = new HashMap<>(); - - public BepServer(BazelBspServer bspServer, BuildClient bspClient) { - this.bspServer = bspServer; - this.bspClient = bspClient; - } - - public static StatusCode convertExitCode(int exitCode) { - switch (exitCode) { - case 0: - return StatusCode.forValue(1); - case 8: - return StatusCode.forValue(3); - default: - return StatusCode.forValue(2); - } - } - - @Override - public void publishLifecycleEvent( - PublishLifecycleEventRequest request, StreamObserver responseObserver) { - namedSetsOfFiles.clear(); - responseObserver.onNext(Empty.getDefaultInstance()); - responseObserver.onCompleted(); - } - - @Override - public StreamObserver publishBuildToolEventStream( - StreamObserver responseObserver) { - return new StreamObserver() { - - @Override - public void onNext(PublishBuildToolEventStreamRequest request) { - if (request - .getOrderedBuildEvent() - .getEvent() - .getBazelEvent() - .getTypeUrl() - .equals("type.googleapis.com/build_event_stream.BuildEvent")) { - handleEvent(request.getOrderedBuildEvent().getEvent()); - } else { - // System.out.println("Got this request " + request); - } - PublishBuildToolEventStreamResponse response = - PublishBuildToolEventStreamResponse.newBuilder() - .setStreamId(request.getOrderedBuildEvent().getStreamId()) - .setSequenceNumber(request.getOrderedBuildEvent().getSequenceNumber()) - .build(); - responseObserver.onNext(response); - } - - private void handleEvent(BuildEvent buildEvent) { - try { - BuildEventStreamProtos.BuildEvent event = - BuildEventStreamProtos.BuildEvent.parseFrom(buildEvent.getBazelEvent().getValue()); - System.out.println("Got event" + event + "\nevent-end\n"); - if (event.hasStarted() && event.getStarted().getCommand().equals("build")) { - BuildEventStreamProtos.BuildStarted buildStarted = event.getStarted(); - TaskId taskId = new TaskId(buildStarted.getUuid()); - TaskStartParams startParams = new TaskStartParams(taskId); - startParams.setEventTime(buildStarted.getStartTimeMillis()); - bspClient.onBuildTaskStart(startParams); - taskParkingLot.add(taskId); - } - if (event.hasFinished()) { - BuildEventStreamProtos.BuildFinished buildFinished = event.getFinished(); - if (taskParkingLot.size() == 0) { - System.out.println("No start event id was found."); - return; - } else if (taskParkingLot.size() > 1) { - System.out.println("More than 1 start event was found"); - return; - } - - TaskFinishParams finishParams = - new TaskFinishParams( - taskParkingLot.pop(), convertExitCode(buildFinished.getExitCode().getCode())); - finishParams.setEventTime(buildFinished.getFinishTimeMillis()); - bspClient.onBuildTaskFinish(finishParams); - } - - if (event.getId().hasNamedSet()) { - namedSetsOfFiles.put(event.getId().getNamedSet().getId(), event.getNamedSetOfFiles()); - } - if (event.hasCompleted()) { - processCompletionEvent(event); - } - if (event.hasAction()) { - processActionDiagnostics(event); - } - if (event.hasAborted()) { - processAbortion(event.getAborted()); - } - if (event.hasProgress()) { - BuildEventStreamProtos.Progress progress = event.getProgress(); - processStdErrDiagnostics(progress); - String message = progress.getStderr().trim(); - if (!message.isEmpty()) bspServer.logMessage(progress.getStderr().trim()); - } - - } catch (IOException e) { - System.err.println("Error deserializing BEP proto: " + e); - } - } - - @Override - public void onError(Throwable throwable) { - System.out.println("Error from BEP stream: " + throwable); - } - - @Override - public void onCompleted() { - responseObserver.onCompleted(); - } - }; - } - - private void processAbortion(BuildEventStreamProtos.Aborted aborted) { - if (aborted.getReason() != BuildEventStreamProtos.Aborted.AbortReason.NO_BUILD) - bspServer.logError( - "Command aborted with reason " + aborted.getReason() + ": " + aborted.getDescription()); - } - - private void processStdErrDiagnostics(BuildEventStreamProtos.Progress progress) { - HashMap> fileDiagnostics = new HashMap<>(); - Arrays.stream(progress.getStderr().split("\n")) - .filter( - error -> - error.contains("ERROR") - && (error.contains("/" + workspace + ":") || error.contains("/" + build + ":"))) - .forEach( - error -> { - String erroredFile = error.contains(workspace) ? workspace : build; - String[] lineLocation; - String fileLocation; - - if (error.contains(" at /")) { - int endOfMessage = error.indexOf(" at /"); - String fileInfo = error.substring(endOfMessage + 4); - int urlEnd = fileInfo.indexOf(erroredFile) + erroredFile.length(); - fileLocation = fileInfo.substring(0, urlEnd); - lineLocation = fileInfo.substring(urlEnd + 1).split(":"); - } else { - int urlEnd = error.indexOf(erroredFile) + erroredFile.length(); - fileLocation = error.substring(error.indexOf("ERROR: ") + 7, urlEnd); - lineLocation = error.substring(urlEnd + 1).split("(:)|( )"); - } - System.out.println("Error: " + error); - System.out.println("File location: " + fileLocation); - System.out.println("Line location: " + Arrays.toString(lineLocation)); - Position position = - new Position( - Integer.parseInt(lineLocation[0]), Integer.parseInt(lineLocation[1])); - Diagnostic diagnostic = new Diagnostic(new Range(position, position), error); - diagnostic.setSeverity(DiagnosticSeverity.ERROR); - List diagnostics = - fileDiagnostics.getOrDefault(fileLocation, new ArrayList<>()); - diagnostics.add(diagnostic); - fileDiagnostics.put(fileLocation, diagnostics); - }); - - fileDiagnostics.forEach( - (fileLocation, diagnostics) -> - bspClient.onBuildPublishDiagnostics( - new PublishDiagnosticsParams( - new TextDocumentIdentifier(Uri.fromAbsolutePath(fileLocation).toString()), - new BuildTargetIdentifier(""), - diagnostics, - false))); - } - - private void processCompletionEvent(BuildEventStreamProtos.BuildEvent event) throws IOException { - List outputGroups = - event.getCompleted().getOutputGroupList(); - if (outputGroups.size() == 1) { - BuildEventStreamProtos.OutputGroup outputGroup = outputGroups.get(0); - if ("scala_compiler_classpath_files".equals(outputGroup.getName())) { - for (BuildEventStreamProtos.BuildEventId.NamedSetOfFilesId fileSetId : - outputGroup.getFileSetsList()) { - for (BuildEventStreamProtos.File file : - namedSetsOfFiles.get(fileSetId.getId()).getFilesList()) { - URI protoPathUri; - try { - protoPathUri = new URI(file.getUri()); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - List lines = - com.google.common.io.Files.readLines( - new File(protoPathUri), StandardCharsets.UTF_8); - for (String line : lines) { - List parts = Splitter.on("\"").splitToList(line); - if (parts.size() != 3) { - throw new RuntimeException("Wrong parts in sketchy textproto parsing: " + parts); - } - compilerClasspath.add( - Uri.fromExecPath("exec-root://" + parts.get(1), bspServer.getExecRoot())); - } - } - } - } - } - } - - private void processActionDiagnostics(BuildEventStreamProtos.BuildEvent event) - throws IOException { - BuildEventStreamProtos.ActionExecuted action = event.getAction(); - String actionType = action.getType(); - if (!SUPPORTED_ACTIONS.contains(actionType)) { - // Ignore file template writes and such. - // TODO(illicitonion): Maybe include them as task notifications (rather than diagnostics). - return; - } - System.out.println("DWH: Event: " + event + "\n\n"); - Map> filesToDiagnostics = new HashMap<>(); - BuildTargetIdentifier target = new BuildTargetIdentifier(action.getLabel()); - boolean hasDiagnosticsOutput = diagnosticsProtosLocations.containsKey(target.getUri()); - for (BuildEventStreamProtos.File log : action.getActionMetadataLogsList()) { - if (!log.getName().equals("diagnostics")) continue; - - System.out.println("Found diagnostics file in " + log.getUri()); - if (hasDiagnosticsOutput) diagnosticsProtosLocations.remove(target.getUri()); - getDiagnostics(filesToDiagnostics, target, log.getUri().substring(7)); - } - - if (filesToDiagnostics.isEmpty() && hasDiagnosticsOutput) { - getDiagnostics(filesToDiagnostics, target, diagnosticsProtosLocations.get(target.getUri())); - diagnosticsProtosLocations.remove(target.getUri()); - } - - emitDiagnostics(filesToDiagnostics, target); - } - - public void emitDiagnostics( - Map> filesToDiagnostics, BuildTargetIdentifier target) { - for (SourceItem source : bspServer.getCachedBuildTargetSources(target)) { - Uri sourceUri = Uri.fromFileUri(source.getUri()); - if (!filesToDiagnostics.containsKey(sourceUri)) { - filesToDiagnostics.put( - sourceUri, - Lists.newArrayList( - new PublishDiagnosticsParams( - new TextDocumentIdentifier(sourceUri.toString()), - target, - new ArrayList<>(), - true))); - } - if (bspClient != null) { - for (List values : filesToDiagnostics.values()) { - for (PublishDiagnosticsParams param : values) { - bspClient.onBuildPublishDiagnostics(param); - } - } - } - } - } - - public void getDiagnostics( - Map> filesToDiagnostics, - BuildTargetIdentifier target, - String diagnosticsLocation) - throws IOException { - Diagnostics.TargetDiagnostics targetDiagnostics = - Diagnostics.TargetDiagnostics.parseFrom(Files.readAllBytes(Paths.get(diagnosticsLocation))); - for (Diagnostics.FileDiagnostics fileDiagnostics : targetDiagnostics.getDiagnosticsList()) { - System.out.println("Inserting diagnostics for path: " + fileDiagnostics.getPath()); - filesToDiagnostics.put( - Uri.fromExecOrWorkspacePath( - fileDiagnostics.getPath(), bspServer.getExecRoot(), bspServer.getWorkspaceRoot()), - convert(target, fileDiagnostics)); - } - } - - private List convert( - BuildTargetIdentifier target, Diagnostics.FileDiagnostics request) { - List diagnostics = new ArrayList<>(); - for (Diagnostics.Diagnostic diagProto : request.getDiagnosticsList()) { - DiagnosticSeverity severity = null; - Diagnostics.Severity protoSeverity = diagProto.getSeverity(); - if (protoSeverity.equals(Diagnostics.Severity.ERROR)) { - severity = DiagnosticSeverity.ERROR; - } else if (protoSeverity.equals(Diagnostics.Severity.WARNING)) { - severity = DiagnosticSeverity.WARNING; - } else if (protoSeverity.equals(Diagnostics.Severity.INFORMATION)) { - severity = DiagnosticSeverity.INFORMATION; - } else if (protoSeverity.equals(Diagnostics.Severity.HINT)) { - severity = DiagnosticSeverity.HINT; - } else if (protoSeverity.equals(Diagnostics.Severity.UNKNOWN)) { - severity = DiagnosticSeverity.ERROR; - } - - Diagnostic diagnostic = - new Diagnostic( - new Range( - new Position( - diagProto.getRange().getStart().getLine(), - diagProto.getRange().getStart().getCharacter()), - new Position( - diagProto.getRange().getEnd().getLine(), - diagProto.getRange().getEnd().getCharacter())), - diagProto.getMessage()); - if (severity != null) { - diagnostic.setSeverity(severity); - } - diagnostics.add(diagnostic); - } - return Lists.newArrayList( - new PublishDiagnosticsParams( - new TextDocumentIdentifier( - Uri.fromExecOrWorkspacePath( - request.getPath(), bspServer.getExecRoot(), bspServer.getWorkspaceRoot()) - .toString()), - target, - diagnostics, - true)); - } - - public TreeSet fetchScalacClasspath() { - return compilerClasspath; - } - - public Map getDiagnosticsProtosLocations() { - return diagnosticsProtosLocations; - } - - public void setDiagnosticsProtosLocations(Map diagnosticsProtosLocations) { - this.diagnosticsProtosLocations = diagnosticsProtosLocations; - } -} diff --git a/main/src/org/jetbrains/bsp/bazel/Constants.java b/main/src/org/jetbrains/bsp/bazel/Constants.java deleted file mode 100644 index 74086299d..000000000 --- a/main/src/org/jetbrains/bsp/bazel/Constants.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.jetbrains.bsp.bazel; - -public class Constants { - public static final String NAME = "bazelbsp"; - public static final String VERSION = "0.0.0"; - public static final String BSP_VERSION = "2.0.0"; -} diff --git a/main/src/org/jetbrains/bsp/bazel/Install.java b/main/src/org/jetbrains/bsp/bazel/Install.java index 336ae077f..e69de29bb 100644 --- a/main/src/org/jetbrains/bsp/bazel/Install.java +++ b/main/src/org/jetbrains/bsp/bazel/Install.java @@ -1,69 +0,0 @@ -package org.jetbrains.bsp.bazel; - -import ch.epfl.scala.bsp4j.BspConnectionDetails; -import com.google.common.base.Splitter; -import com.google.gson.GsonBuilder; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.stream.Collectors; - -public class Install { - public static void main(String[] args) throws IOException { - List argv = new ArrayList<>(); - argv.add(Paths.get(System.getProperty("java.home")).resolve("bin").resolve("java").toString()); - argv.add("-classpath"); - String classpath = - Splitter.on(":").splitToList(System.getProperty("java.class.path")).stream() - .map(elem -> Paths.get(elem).toAbsolutePath().toString()) - .collect(Collectors.joining(":")); - argv.add(classpath); - argv.add("org.jetbrains.bsp.bazel.Server"); - argv.add("bsp"); - argv.add(findOnPath("bazel")); - BspConnectionDetails details = - new BspConnectionDetails( - Constants.NAME, - argv, - Constants.VERSION, - Constants.BSP_VERSION, - BazelBspServer.SUPPORTED_LANGUAGES); - Path bspDir = args.length > 1 ? Paths.get(args[1]).resolve(".bsp") : Paths.get(".bsp"); - Files.createDirectories(bspDir); - String aspectsFile = "aspects.bzl"; - Path home = Paths.get(".bazelbsp").toAbsolutePath(); - Files.createDirectories(home); - Files.copy( - Server.class.getResourceAsStream(aspectsFile), - home.resolve(aspectsFile), - StandardCopyOption.REPLACE_EXISTING); - Files.newByteChannel( - home.resolve("BUILD"), StandardOpenOption.CREATE, StandardOpenOption.WRITE); - Files.write( - bspDir.resolve("bazelbsp.json"), - new GsonBuilder() - .setPrettyPrinting() - .create() - .toJson(details) - .getBytes(StandardCharsets.UTF_8)); - } - - protected static String findOnPath(String bin) { - List pathElements = Splitter.on(File.pathSeparator).splitToList(System.getenv("PATH")); - for (String pathElement : pathElements) { - File maybePath = new File(pathElement, bin); - if (maybePath.canExecute()) { - return maybePath.toString(); - } - } - throw new NoSuchElementException("Could not find bazel on your PATH"); - } -} diff --git a/main/src/org/jetbrains/bsp/bazel/Server.java b/main/src/org/jetbrains/bsp/bazel/Server.java deleted file mode 100644 index 01ea8f61b..000000000 --- a/main/src/org/jetbrains/bsp/bazel/Server.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.jetbrains.bsp.bazel; - -import ch.epfl.scala.bsp4j.BuildClient; -import io.grpc.ServerBuilder; -import java.io.InputStream; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.eclipse.lsp4j.jsonrpc.Launcher; - -public class Server { - public static void main(String[] args) throws Exception { - if (args.length == 0) { - System.err.printf("Expected a command; got args: %s%n", Arrays.toString(args)); - System.exit(1); - } - - if (args[0].equals("bsp")) { - PrintStream stdout = System.out; - InputStream stdin = System.in; - - Path home = Paths.get(".bazelbsp").toAbsolutePath(); - Files.createDirectories(home); - Path log = home.resolve("bazelbsp.log"); - PrintStream logStream = - new PrintStream( - Files.newOutputStream( - log, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)); - - Path traceFile = home.resolve("bazelbsp.trace.json"); - PrintWriter traceWriter = - new PrintWriter( - Files.newOutputStream( - traceFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)); - System.setOut(logStream); - System.setErr(logStream); - ExecutorService executor = Executors.newCachedThreadPool(); - try { - BazelBspServer bspServer = new BazelBspServer(args[1]); - Launcher launcher = - new Launcher.Builder() - .traceMessages(traceWriter) - .setOutput(stdout) - .setInput(stdin) - .setLocalService(bspServer) - .setRemoteInterface(BuildClient.class) - .setExecutorService(executor) - .create(); - bspServer.setBuildClient(launcher.getRemoteProxy()); - BepServer bepServer = new BepServer(bspServer, launcher.getRemoteProxy()); - bspServer.bepServer = bepServer; - io.grpc.Server server = ServerBuilder.forPort(0).addService(bepServer).build().start(); - bspServer.setBackendPort(server.getPort()); - launcher.startListening(); - server.awaitTermination(); - } finally { - executor.shutdown(); - } - } else if (args[0].equals("bep")) { - String bazel = args.length > 1 ? args[1] : Install.findOnPath("bazel"); - io.grpc.Server bepServer = - ServerBuilder.forPort(0) - .addService(new BepServer(new BazelBspServer(bazel), null)) - .build() - .start(); - bepServer.awaitTermination(); - } - } -} diff --git a/main/src/org/jetbrains/bsp/bazel/aspects.bzl b/main/src/org/jetbrains/bsp/bazel/aspects.bzl deleted file mode 100644 index e11d202bf..000000000 --- a/main/src/org/jetbrains/bsp/bazel/aspects.bzl +++ /dev/null @@ -1,30 +0,0 @@ -def _print_aspect_impl(target, ctx): - if hasattr(ctx.rule.attr, "srcjar"): - srcjar = ctx.rule.attr.srcjar - if srcjar != None: - for f in srcjar.files.to_list(): - if f != None: - print(f.path) - return [] - -print_aspect = aspect( - implementation = _print_aspect_impl, - attr_aspects = ["deps"], -) - -def _scala_compiler_classpath_impl(target, ctx): - files = depset() - if hasattr(ctx.rule.attr, "jars"): - for target in ctx.rule.attr.jars: - files = depset(transitive = [files, target.files]) - - compiler_classpath_file = ctx.actions.declare_file("%s.textproto" % target.label.name) - ctx.actions.write(compiler_classpath_file, struct(files = [file.path for file in files.to_list()]).to_proto()) - - return [ - OutputGroupInfo(scala_compiler_classpath_files = [compiler_classpath_file]), - ] - -scala_compiler_classpath_aspect = aspect( - implementation = _scala_compiler_classpath_impl, -) diff --git a/main/test/org/jetbrains/bsp/bazel/BUILD b/main/test/org/jetbrains/bsp/bazel/BUILD deleted file mode 100644 index 7c7014eb0..000000000 --- a/main/test/org/jetbrains/bsp/bazel/BUILD +++ /dev/null @@ -1,12 +0,0 @@ -java_binary( - name = "bsp-test", - srcs = ["BazelBspServerTest.java"], - main_class = "org.jetbrains.bsp.bazel.BazelBspServerTest", - deps = [ - "//main/src/org/jetbrains/bsp/bazel:bsp", - "@maven//:ch_epfl_scala_bsp4j", - "@maven//:ch_epfl_scala_bsp_testkit_2_13", - "@maven//:com_google_guava_guava", - "@maven//:org_scala_lang_scala_library", - ], -) diff --git a/main/test/org/jetbrains/bsp/bazel/BazelBspServerTest.java b/main/test/org/jetbrains/bsp/bazel/BazelBspServerTest.java index 83e3c25d2..e69de29bb 100644 --- a/main/test/org/jetbrains/bsp/bazel/BazelBspServerTest.java +++ b/main/test/org/jetbrains/bsp/bazel/BazelBspServerTest.java @@ -1,124 +0,0 @@ -package org.jetbrains.bsp.bazel; - -import ch.epfl.scala.bsp.testkit.client.TestClient; -import ch.epfl.scala.bsp.testkit.client.TestClient$; -import ch.epfl.scala.bsp4j.*; -import com.google.common.collect.Lists; -import java.time.Duration; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.*; -import java.util.stream.Collectors; -import scala.concurrent.ExecutionContext; - -public class BazelBspServerTest { - private final String outDirectory = "bazel-out"; - private final String workspace; - private final TestClient client; - private final ExecutorService executorService = Executors.newCachedThreadPool(); - private final ExecutionContext context = ExecutionContext.fromExecutor(executorService); - - private final BuildTargetIdentifier id1 = new BuildTargetIdentifier("//example:example"); - private final BuildTargetIdentifier id2 = new BuildTargetIdentifier("//dep:dep"); - private final BuildTargetIdentifier id3 = new BuildTargetIdentifier("//dep/deeper:deeper"); - private final WorkspaceBuildTargetsResult expectedBuildTargets = - new WorkspaceBuildTargetsResult( - Lists.newArrayList( - new BuildTarget( - id1, - Lists.newArrayList(), - Lists.newArrayList("scala"), - Lists.newArrayList(id2), - new BuildTargetCapabilities(true, false, true)), - new BuildTarget( - id2, - Lists.newArrayList(), - Lists.newArrayList("java", "scala"), - Lists.newArrayList(id3), - new BuildTargetCapabilities(true, false, false)))); - private final List dependencies = - Lists.newArrayList( - "https/repo1.maven.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3-sources.jar", - "https/repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar", - "https/repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1-sources.jar", - "https/repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.3.2/error_prone_annotations-2.3.2-sources.jar", - "https/repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17-sources.jar", - "https/repo1.maven.org/maven2/org/checkerframework/checker-qual/2.8.1/checker-qual-2.8.1-sources.jar", - "https/repo1.maven.org/maven2/com/google/guava/guava/28.0-jre/guava-28.0-jre-sources.jar"); - private final DependencySourcesResult expectedDependencies = - new DependencySourcesResult( - Lists.newArrayList( - new DependencySourcesItem(id1, dependencies), - new DependencySourcesItem(id2, dependencies))); - private final SourcesResult expectedSources = - new SourcesResult( - Lists.newArrayList( - new SourcesItem( - id1, - Lists.newArrayList( - new SourceItem( - "sample-repo/example/Example.scala", SourceItemKind.FILE, false))), - new SourcesItem( - id2, - Lists.newArrayList( - new SourceItem("sample-repo/dep/Test.scala", SourceItemKind.FILE, false), - new SourceItem("sample-repo/dep/JavaTest.java", SourceItemKind.FILE, false), - new SourceItem("sample-repo/dep/Dep.scala", SourceItemKind.FILE, false))))); - - private final ResourcesResult expectedResources = - new ResourcesResult( - Lists.newArrayList( - new ResourcesItem( - id1, - Lists.newArrayList( - "sample-repo/example/file.txt", "sample-repo/example/file2.txt")))); - - private final InverseSourcesResult expectedInverseSources = - new InverseSourcesResult(Lists.newArrayList(id2)); - - public BazelBspServerTest(String workspace) { - this.workspace = workspace; - this.client = - TestClient$.MODULE$.testInitialStructure(workspace, new HashMap<>(), Duration.ofMinutes(2)); - - Runnable[] tests = { - client::testResolveProject, - () -> client.testCompareWorkspaceTargetsResults(expectedBuildTargets), - () -> client.testSourcesResults(expectedBuildTargets, expectedSources), - () -> client.testResourcesResults(expectedBuildTargets, expectedResources), - () -> - client.testInverseSourcesResults( - new TextDocumentIdentifier("file://" + workspace + "/dep/Dep.scala"), - expectedInverseSources), - () -> client.testDependencySourcesResults(expectedBuildTargets, expectedDependencies), - // client::testTargetsRunUnsuccessfully, - // client::testTargetsTestUnsuccessfully, - // client::testTargetCapabilities, - }; - runTests(tests); - } - - public static void main(String[] args) { - new BazelBspServerTest(System.getenv("BUILD_WORKSPACE_DIRECTORY") + "/sample-repo"); - } - - private void runTests(Runnable[] tests) { - List> futures = - Arrays.stream(tests).map(executorService::submit).collect(Collectors.toList()); - boolean failed = false; - for (Future future : futures) { - try { - future.get(6, TimeUnit.MINUTES); - } catch (InterruptedException | TimeoutException e) { - System.err.println("Something wrong happened while running the test"); - failed = true; - } catch (ExecutionException e) { - System.err.println(e.getMessage()); - failed = true; - } - } - - System.exit(failed ? 1 : 0); - } -} diff --git a/sample-repo/dep/BUILD b/sample-repo/dep/BUILD deleted file mode 100644 index 079f07690..000000000 --- a/sample-repo/dep/BUILD +++ /dev/null @@ -1,11 +0,0 @@ -load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library") - -scala_library( - name = "dep", - srcs = glob([ - "*.java", - "*.scala", - ]), - visibility = ["//visibility:public"], - deps = ["//dep/deeper"], -) diff --git a/src/main/java/org/jetbrains/bsp/bazel/BUILD b/src/main/java/org/jetbrains/bsp/bazel/BUILD new file mode 100644 index 000000000..19f4ec222 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/BUILD @@ -0,0 +1,30 @@ +load("@rules_java//java:defs.bzl", "java_binary") +load("@bazel_sonatype//:defs.bzl", "sonatype_java_export") + +sonatype_java_export( + name = "bsp", + srcs = glob(["*.java"]), + maven_coordinates = "org.jetbrains.bsp:bazel-bsp:1.0.0-RC1", + maven_profile = "org.jetbrains", + pom_template = "//:pom.xml", + resources = ["//src/main/resources:bsp-main-resources"], + visibility = ["//visibility:public"], + runtime_deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/install", + "//src/main/java/org/jetbrains/bsp/bazel/server", + ], +) + +java_binary( + name = "bsp-install", + srcs = glob(["*.java"]), + main_class = "org.jetbrains.bsp.bazel.install.Install", + resources = ["//src/main/resources:bsp-main-resources"], + visibility = ["//visibility:public"], + runtime_deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/install", + "//src/main/java/org/jetbrains/bsp/bazel/server", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/commons/BUILD b/src/main/java/org/jetbrains/bsp/bazel/commons/BUILD new file mode 100644 index 000000000..09d9809bd --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/commons/BUILD @@ -0,0 +1,10 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "commons", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "@maven//:com_google_guava_guava", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/commons/Constants.java b/src/main/java/org/jetbrains/bsp/bazel/commons/Constants.java new file mode 100644 index 000000000..b57b747c3 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/commons/Constants.java @@ -0,0 +1,70 @@ +package org.jetbrains.bsp.bazel.commons; + +import com.google.common.collect.ImmutableList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class Constants { + + public static final String NAME = "bazelbsp"; + public static final String VERSION = "0.0.0"; + public static final String BSP_VERSION = "2.0.0"; + + public static final String SCALA = "scala"; + public static final String JAVA = "java"; + public static final String KOTLIN = "kotlin"; + public static final String CPP = "cpp"; + + public static final String SCALAC = "Scalac"; + public static final String JAVAC = "Javac"; + public static final String KOTLINC = "KotlinCompile"; + + public static final List SUPPORTED_LANGUAGES = ImmutableList.of(SCALA, JAVA, KOTLIN); + public static final List SUPPORTED_COMPILERS = ImmutableList.of(SCALAC, JAVAC, KOTLINC); + + public static final List SCALA_EXTENSIONS = ImmutableList.of(".scala"); + public static final List JAVA_EXTENSIONS = ImmutableList.of(".java"); + public static final List KOTLIN_EXTENSIONS = ImmutableList.of(".kt"); + public static final List CPP_EXTENSIONS = + ImmutableList.of(".C", ".cc", ".cpp", ".CPP", ".c++", ".cp", "cxx", ".h", ".hpp"); + + public static final String MAIN_CLASS_ATTR_NAME = "main_class"; + public static final String ARGS_ATTR_NAME = "args"; + public static final String JVM_FLAGS_ATTR_NAME = "jvm_flags"; + + public static final List OTHER_FILE_EXTENSIONS = + ImmutableList.of(".kts", ".sh", ".bzl", ".py", ".js", ".c", ".h", ".cpp", ".hpp"); + + public static final List FILE_EXTENSIONS = + ImmutableList.of(SCALA_EXTENSIONS, JAVA_EXTENSIONS, KOTLIN_EXTENSIONS, OTHER_FILE_EXTENSIONS) + .stream() + .flatMap(Collection::stream) + .collect(Collectors.toList()); + + public static final String BAZEL_BUILD_COMMAND = "build"; + + public static final String BINARY_RULE_TYPE = "binary"; + public static final String LIBRARY_RULE_TYPE = "library"; + public static final String TEST_RULE_TYPE = "test"; + + public static final String BUILD_FILE_NAME = "BUILD"; + public static final String WORKSPACE_FILE_NAME = "WORKSPACE"; + + public static final String ASPECTS_FILE_NAME = "aspects.bzl"; + public static final String BAZELBSP_DIR_NAME = ".bazelbsp"; + public static final String BSP_DIR_NAME = ".bsp"; + public static final String BAZELBSP_JSON_FILE_NAME = "bazelbsp.json"; + public static final String BAZELBSP_LOG_FILE_NAME = "bazelbsp.log"; + public static final String BAZELBSP_TRACE_JSON_FILE_NAME = "bazelbsp.trace.json"; + + public static final List KNOWN_SOURCE_ROOTS = + ImmutableList.of("java", "scala", "kotlin", "javatests", "src", "test", "main", "testsrc"); + public static final String DIAGNOSTICS = "diagnostics"; + public static final String EXEC_ROOT_PREFIX = "exec-root://"; + public static final String SCALA_COMPILER_CLASSPATH_FILES = "scala_compiler_classpath_files"; + + public static final String SCALA_TEST_MAIN_CLASSES_ATTRIBUTE_NAME = "main_class"; + + public static final String DEFAULT_PROJECT_VIEW_FILE = "projectview.bazelproject"; +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/commons/Lazy.java b/src/main/java/org/jetbrains/bsp/bazel/commons/Lazy.java new file mode 100644 index 000000000..9ae6eea09 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/commons/Lazy.java @@ -0,0 +1,22 @@ +package org.jetbrains.bsp.bazel.commons; + +import java.util.Optional; +import java.util.function.Supplier; + +public abstract class Lazy { + private Optional> value = Optional.empty(); + + protected abstract Supplier> calculateValue(); + + public Optional getValue() { + if (!value.isPresent()) { + value = Optional.of(calculateValue().get()); + } + + return value.get(); + } + + public void recalculateValue() { + value = Optional.of(calculateValue().get()); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/commons/ListUtils.java b/src/main/java/org/jetbrains/bsp/bazel/commons/ListUtils.java new file mode 100644 index 000000000..bf5357a5c --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/commons/ListUtils.java @@ -0,0 +1,12 @@ +package org.jetbrains.bsp.bazel.commons; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class ListUtils { + + public static List concat(List list1, List list2) { + return Stream.concat(list1.stream(), list2.stream()).collect(Collectors.toList()); + } +} diff --git a/main/src/org/jetbrains/bsp/bazel/Uri.java b/src/main/java/org/jetbrains/bsp/bazel/commons/Uri.java similarity index 95% rename from main/src/org/jetbrains/bsp/bazel/Uri.java rename to src/main/java/org/jetbrains/bsp/bazel/commons/Uri.java index 8eb8981e2..d12a09baf 100644 --- a/main/src/org/jetbrains/bsp/bazel/Uri.java +++ b/src/main/java/org/jetbrains/bsp/bazel/commons/Uri.java @@ -1,13 +1,12 @@ -package org.jetbrains.bsp.bazel; +package org.jetbrains.bsp.bazel.commons; import com.google.common.base.Splitter; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.List; public class Uri implements Comparable { - private static final String ENC = StandardCharsets.UTF_8.toString(); + private final URI uri; private Uri(String uri) { diff --git a/src/main/java/org/jetbrains/bsp/bazel/install/BUILD b/src/main/java/org/jetbrains/bsp/bazel/install/BUILD new file mode 100644 index 000000000..b213b78a6 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/install/BUILD @@ -0,0 +1,21 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +java_library( + name = "install", + srcs = glob(["*.java"]), + resources = ["aspects.bzl"], + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_code_gson_gson", + "@maven//:com_google_guava_guava", + "@maven//:commons_cli_commons_cli", + ], +) + +java_binary( + name = "bsp-install", + main_class = "org.jetbrains.bsp.bazel.install.Install", + runtime_deps = ["//src/main/java/org/jetbrains/bsp/bazel/install"], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/install/Install.java b/src/main/java/org/jetbrains/bsp/bazel/install/Install.java new file mode 100644 index 000000000..6ba099c6e --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/install/Install.java @@ -0,0 +1,272 @@ +package org.jetbrains.bsp.bazel.install; + +import ch.epfl.scala.bsp4j.BspConnectionDetails; +import com.google.common.base.Splitter; +import com.google.gson.GsonBuilder; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.jetbrains.bsp.bazel.commons.Constants; + +public class Install { + + public static final String INSTALLER_BINARY_NAME = "bazelbsp-install"; + public static final String SERVER_CLASS_NAME = "org.jetbrains.bsp.bazel.server.ServerInitializer"; + + private static final String DEBUGGER_SHORT_OPT = "x"; + private static final String JAVA_SHORT_OPT = "j"; + private static final String BAZEL_SHORT_OPT = "b"; + private static final String HELP_SHORT_OPT = "h"; + private static final String DIRECTORY_SHORT_OPT = "d"; + private static final String BAZEL_TARGETS_SHORT_OPT = "t"; + + public static void main(String[] args) throws IOException { + // This is the command line which will be used to start the BSP server. See: + // https://build-server-protocol.github.io/docs/server-discovery.html#build-tool-commands-to-start-bsp-servers + List argv = new ArrayList<>(); + + Options cliOptions = getCliOptions(); + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + formatter.setWidth(120); + boolean hasError = false; + PrintWriter writer = new PrintWriter(System.err); + try { + CommandLine cmd = parser.parse(cliOptions, args, false); + if (cmd.hasOption(HELP_SHORT_OPT)) { + formatter.printHelp(INSTALLER_BINARY_NAME, cliOptions); + } else { + addJavaBinary(cmd, argv); + addJavaClasspath(argv); + addDebuggerConnection(cmd, argv); + argv.add(SERVER_CLASS_NAME); + addBazelBinary(cmd, argv); + addBazelTargets(cmd, argv); + + BspConnectionDetails details = createBspConnectionDetails(argv); + Path rootDir = getRootDir(cmd); + writeConfigurationFiles(rootDir, details); + + String currentDirectory = getCurrentDirectory(); + System.out.println("Bazel BSP server installed in '" + currentDirectory + "'."); + } + } catch (ParseException e) { + writer.println(e.getMessage()); + formatter.printUsage(writer, 120, INSTALLER_BINARY_NAME, cliOptions); + hasError = true; + } catch (NoSuchElementException e) { + writer.println(e.getMessage()); + hasError = true; + } finally { + writer.close(); + } + + if (hasError) { + System.exit(1); + } + } + + private static Path getRootDir(CommandLine cmd) { + return cmd.hasOption(DIRECTORY_SHORT_OPT) + ? Paths.get(cmd.getOptionValue(DIRECTORY_SHORT_OPT)) + : Paths.get(""); + } + + private static BspConnectionDetails createBspConnectionDetails(List argv) { + return new BspConnectionDetails( + Constants.NAME, + argv, + Constants.VERSION, + Constants.BSP_VERSION, + Constants.SUPPORTED_LANGUAGES); + } + + private static void addBazelBinary(CommandLine cmd, List argv) { + if (cmd.hasOption(BAZEL_SHORT_OPT)) { + argv.add(cmd.getOptionValue(BAZEL_SHORT_OPT)); + } else { + argv.add(findOnPath("bazel")); + } + } + + private static void addJavaBinary(CommandLine cmd, List argv) { + if (cmd.hasOption(JAVA_SHORT_OPT)) { + argv.add(cmd.getOptionValue(JAVA_SHORT_OPT)); + } else { + String javaHome = readSystemProperty("java.home"); + argv.add(Paths.get(javaHome).resolve("bin").resolve("java").toString()); + } + } + + private static void addJavaClasspath(List argv) { + argv.add("-classpath"); + String javaClassPath = readSystemProperty("java.class.path"); + String classpath = + Splitter.on(":").splitToList(javaClassPath).stream() + .map(elem -> Paths.get(elem).toAbsolutePath().toString()) + .collect(Collectors.joining(":")); + + argv.add(classpath); + } + + private static void addDebuggerConnection(CommandLine cmd, List argv) { + if (cmd.hasOption(DEBUGGER_SHORT_OPT)) { + String debuggerAddress = cmd.getOptionValue(DEBUGGER_SHORT_OPT); + argv.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=" + debuggerAddress); + } + } + + private static void addBazelTargets(CommandLine cmd, List argv) { + if (cmd.hasOption(BAZEL_TARGETS_SHORT_OPT)) { + String targets = cmd.getOptionValue(BAZEL_TARGETS_SHORT_OPT); + argv.add(targets); + } + } + + private static void copyAspects(Path bazelbspDir) throws IOException { + Path aspectsFile = bazelbspDir.resolve(Constants.ASPECTS_FILE_NAME); + Files.copy( + Install.class.getResourceAsStream(Constants.ASPECTS_FILE_NAME), + aspectsFile, + StandardCopyOption.REPLACE_EXISTING); + } + + /** Create an empty BUILD file, overwrite if exists */ + private static void createEmptyBuildFile(Path bazelbspDir) throws IOException { + Files.newByteChannel( + bazelbspDir.resolve(Constants.BUILD_FILE_NAME), + StandardOpenOption.CREATE, + StandardOpenOption.WRITE); + } + + private static Path createDir(Path rootDir, String name) throws IOException { + Path dir = rootDir.resolve(name); + Files.createDirectories(dir); + return dir; + } + + /** Write bazelbsp.json file which allows bsp server discovery */ + private static void writeBazelbspJson(Path bspDir, Object discoveryDetails) throws IOException { + Files.write( + bspDir.resolve(Constants.BAZELBSP_JSON_FILE_NAME), + new GsonBuilder() + .setPrettyPrinting() + .create() + .toJson(discoveryDetails) + .getBytes(StandardCharsets.UTF_8)); + } + + private static void writeConfigurationFiles(Path rootDir, Object discoveryDetails) + throws IOException { + Path bazelbspDir = createDir(rootDir, Constants.BAZELBSP_DIR_NAME); + copyAspects(bazelbspDir); + createEmptyBuildFile(bazelbspDir); + + Path bspDir = createDir(rootDir, Constants.BSP_DIR_NAME); + writeBazelbspJson(bspDir, discoveryDetails); + } + + private static Options getCliOptions() { + Options cliOptions = new Options(); + + Option java = + Option.builder(JAVA_SHORT_OPT) + .longOpt("java") + .hasArg() + .argName("path") + .desc("Use provided Java executable to run the BSP server") + .build(); + + cliOptions.addOption(java); + + Option bazel = + Option.builder(BAZEL_SHORT_OPT) + .longOpt("bazel") + .hasArg() + .argName("path") + .desc("Make the BSP server use this Bazel executable") + .build(); + + cliOptions.addOption(bazel); + + Option debug = + Option.builder(DEBUGGER_SHORT_OPT) + .longOpt("debugger") + .hasArg() + .argName("address (e.g. '127.0.0.1:8000'") + .desc("Allow BSP server debugging") + .build(); + + cliOptions.addOption(debug); + + Option directory = + Option.builder(DIRECTORY_SHORT_OPT) + .longOpt("directory") + .hasArg() + .argName("path") + .desc( + "Path to directory where bazelbsp server should be setup. " + + "Current directory will be used by default") + .build(); + + cliOptions.addOption(directory); + + Option targets = + Option.builder(BAZEL_TARGETS_SHORT_OPT) + .longOpt("targets") + .hasArg() + .argName("targets") + .desc( + "Name of the bazel's targets that the server should import. Targets can be" + + "separated by a comma. The default is to import all targets (//...)") + .build(); + + cliOptions.addOption(targets); + + Option help = Option.builder(HELP_SHORT_OPT).longOpt("help").desc("Show help").build(); + + cliOptions.addOption(help); + + return cliOptions; + } + + private static String findOnPath(String bin) { + List pathElements = Splitter.on(File.pathSeparator).splitToList(System.getenv("PATH")); + for (String pathElement : pathElements) { + File maybePath = new File(pathElement, bin); + if (maybePath.canExecute()) { + return maybePath.toString(); + } + } + throw new NoSuchElementException("Could not find " + bin + " on your PATH"); + } + + private static String readSystemProperty(String name) { + String property = System.getProperty(name); + if (property == null) { + throw new NoSuchElementException("Could not read " + name + " system property"); + } + return property; + } + + private static String getCurrentDirectory() { + return System.getProperty("user.dir"); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/install/aspects.bzl b/src/main/java/org/jetbrains/bsp/bazel/install/aspects.bzl new file mode 100644 index 000000000..fed768889 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/install/aspects.bzl @@ -0,0 +1,91 @@ +def _print_aspect_impl(target, ctx): + if hasattr(ctx.rule.attr, "srcjar"): + srcjar = ctx.rule.attr.srcjar + if srcjar != None: + for f in srcjar.files.to_list(): + if f != None: + print(f.path) + return [] + +print_aspect = aspect( + implementation = _print_aspect_impl, + attr_aspects = ["deps"], +) + +def _scala_compiler_classpath_impl(target, ctx): + files = depset() + if hasattr(ctx.rule.attr, "jars"): + for target in ctx.rule.attr.jars: + files = depset(transitive = [files, target.files]) + + compiler_classpath_file = ctx.actions.declare_file("%s.textproto" % target.label.name) + ctx.actions.write(compiler_classpath_file, struct(files = [file.path for file in files.to_list()]).to_proto()) + + return [ + OutputGroupInfo(scala_compiler_classpath_files = [compiler_classpath_file]), + ] + +scala_compiler_classpath_aspect = aspect( + implementation = _scala_compiler_classpath_impl, +) + +def _fetch_cpp_compiler(target, ctx): + if cc_common.CcToolchainInfo in target: + toolchain_info = target[cc_common.CcToolchainInfo] + print(toolchain_info.compiler) + print(toolchain_info.compiler_executable) + return [] + +fetch_cpp_compiler = aspect( + implementation = _fetch_cpp_compiler, + fragments = ["cpp"], + attr_aspects = ["_cc_toolchain"], + required_aspect_providers = [[CcInfo]], +) + +def _fetch_java_target_version(target, ctx): + if hasattr(ctx.rule.attr, "target_version"): + print(ctx.rule.attr.target_version) + return [] + +fetch_java_target_version = aspect( + implementation = _fetch_java_target_version, + attr_aspects = ["_java_toolchain"], +) + +def _get_target_info(ctx, field_name): + fields = getattr(ctx.rule.attr, field_name, []) + fields = [ctx.expand_location(field) for field in fields] + fields = [ctx.expand_make_variables(field_name, field, {}) for field in fields] + + return fields + +def _print_fields(fields): + separator = "," + print(separator.join(fields)) + +def _get_cpp_target_info(target, ctx): + if CcInfo not in target: + return [] + + #TODO: Get copts from semantics + copts = _get_target_info(ctx, "copts") + defines = _get_target_info(ctx, "defines") + linkopts = _get_target_info(ctx, "linkopts") + + linkshared = False + if hasattr(ctx.rule.attr, "linkshared"): + linkshared = ctx.rule.attr.linkshared + + _print_fields(copts) + _print_fields(defines) + _print_fields(linkopts) + print(linkshared) + + return [] + +get_cpp_target_info = aspect( + implementation = _get_cpp_target_info, + fragments = ["cpp"], + required_aspect_providers = [[CcInfo]], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/BUILD b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/BUILD new file mode 100644 index 000000000..18862e3a6 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/BUILD @@ -0,0 +1,12 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "model", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific", + "@maven//:com_google_guava_guava", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/ProjectView.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/ProjectView.java new file mode 100644 index 000000000..5b8f7337c --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/ProjectView.java @@ -0,0 +1,90 @@ +package org.jetbrains.bsp.bazel.projectview.model; + +import java.util.Objects; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.DirectoriesSection; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.TargetsSection; + +/** + * Representation of the project view file + * + * @link https://ij.bazel.build/docs/project-views.html + */ +public class ProjectView { + + private final DirectoriesSection directories; + private final TargetsSection targets; + + private ProjectView(DirectoriesSection directories, TargetsSection targets) { + this.directories = directories; + this.targets = targets; + } + + public static ProjectView.Builder builder() { + return new Builder(); + } + + public ProjectView merge(ProjectView projectView) { + return ProjectView.builder() + .directories(directories.merge(projectView.directories)) + .targets(targets.merge(projectView.targets)) + .build(); + } + + public DirectoriesSection getDirectories() { + return directories; + } + + public TargetsSection getTargets() { + return targets; + } + + public static class Builder { + + private DirectoriesSection directories; + private TargetsSection targets; + + public Builder directories(DirectoriesSection directories) { + this.directories = directories; + return this; + } + + public Builder targets(TargetsSection target) { + this.targets = target; + return this; + } + + public ProjectView build() { + assertRequiredFields(); + + return new ProjectView(directories, targets); + } + + private void assertRequiredFields() { + if (directories == null) { + throw new IllegalStateException("directories section is required!"); + } + + if (targets == null) { + throw new IllegalStateException("targets section is required!"); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProjectView that = (ProjectView) o; + return Objects.equals(directories, that.directories) && Objects.equals(targets, that.targets); + } + + @Override + public int hashCode() { + return Objects.hash(directories, targets); + } + + @Override + public String toString() { + return "ProjectView{" + "directories=" + directories + ", targets=" + targets + '}'; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/ProjectViewDefaultProvider.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/ProjectViewDefaultProvider.java new file mode 100644 index 000000000..80e3ffcfd --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/ProjectViewDefaultProvider.java @@ -0,0 +1,19 @@ +package org.jetbrains.bsp.bazel.projectview.model; + +import com.google.common.collect.ImmutableList; +import java.nio.file.Paths; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.DirectoriesSection; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.TargetsSection; + +public class ProjectViewDefaultProvider implements ProjectViewProvider { + + @Override + public ProjectView create() { + DirectoriesSection directoriesSection = + new DirectoriesSection(ImmutableList.of(Paths.get(".")), ImmutableList.of()); + TargetsSection targetsSection = + new TargetsSection(ImmutableList.of("//..."), ImmutableList.of()); + + return ProjectView.builder().directories(directoriesSection).targets(targetsSection).build(); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/ProjectViewProvider.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/ProjectViewProvider.java new file mode 100644 index 000000000..84c277880 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/ProjectViewProvider.java @@ -0,0 +1,6 @@ +package org.jetbrains.bsp.bazel.projectview.model; + +public interface ProjectViewProvider { + + ProjectView create(); +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/BUILD b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/BUILD new file mode 100644 index 000000000..57149e1bb --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/BUILD @@ -0,0 +1,9 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "sections", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/ProjectViewSection.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/ProjectViewSection.java new file mode 100644 index 000000000..c12782476 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/ProjectViewSection.java @@ -0,0 +1,12 @@ +package org.jetbrains.bsp.bazel.projectview.model.sections; + +public abstract class ProjectViewSection> { + + protected final ProjectViewSectionHeader header; + + protected ProjectViewSection(ProjectViewSectionHeader header) { + this.header = header; + } + + public abstract T merge(T otherSection); +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/ProjectViewSectionHeader.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/ProjectViewSectionHeader.java new file mode 100644 index 000000000..5624cc9fd --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/ProjectViewSectionHeader.java @@ -0,0 +1,16 @@ +package org.jetbrains.bsp.bazel.projectview.model.sections; + +public enum ProjectViewSectionHeader { + DIRECTORIES("directories"), + TARGETS("targets"); + + private final String name; + + ProjectViewSectionHeader(String value) { + name = value; + } + + public String toString() { + return name; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific/BUILD b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific/BUILD new file mode 100644 index 000000000..82a98c5b6 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific/BUILD @@ -0,0 +1,11 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "specific", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific/DirectoriesSection.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific/DirectoriesSection.java new file mode 100644 index 000000000..32a7bfc2c --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific/DirectoriesSection.java @@ -0,0 +1,66 @@ +package org.jetbrains.bsp.bazel.projectview.model.sections.specific; + +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import org.jetbrains.bsp.bazel.commons.ListUtils; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSection; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSectionHeader; + +public class DirectoriesSection extends ProjectViewSection { + + private final List includedDirectories; + private final List excludedDirectories; + + public DirectoriesSection(List includedDirectories, List excludedDirectories) { + super(ProjectViewSectionHeader.DIRECTORIES); + this.includedDirectories = includedDirectories; + this.excludedDirectories = excludedDirectories; + } + + @Override + public DirectoriesSection merge(DirectoriesSection otherSection) { + List mergedIncludedDirectories = + ListUtils.concat(includedDirectories, otherSection.includedDirectories); + List mergedExcludedDirectories = + ListUtils.concat(excludedDirectories, otherSection.excludedDirectories); + + return new DirectoriesSection(mergedIncludedDirectories, mergedExcludedDirectories); + } + + public List getIncludedDirectories() { + return includedDirectories; + } + + public List getExcludedDirectories() { + return excludedDirectories; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DirectoriesSection that = (DirectoriesSection) o; + return Objects.equals(includedDirectories, that.includedDirectories) + && Objects.equals(excludedDirectories, that.excludedDirectories); + } + + @Override + public int hashCode() { + return Objects.hash(includedDirectories, excludedDirectories); + } + + @Override + public String toString() { + return "DirectoriesSection{" + + "includedDirectories=" + + includedDirectories + + ", excludedDirectories=" + + excludedDirectories + + '}'; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific/TargetsSection.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific/TargetsSection.java new file mode 100644 index 000000000..892e34515 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific/TargetsSection.java @@ -0,0 +1,65 @@ +package org.jetbrains.bsp.bazel.projectview.model.sections.specific; + +import java.util.List; +import java.util.Objects; +import org.jetbrains.bsp.bazel.commons.ListUtils; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSection; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSectionHeader; + +public class TargetsSection extends ProjectViewSection { + + private final List includedTargets; + private final List excludedTargets; + + public TargetsSection(List includedTargets, List excludedTargets) { + super(ProjectViewSectionHeader.TARGETS); + this.includedTargets = includedTargets; + this.excludedTargets = excludedTargets; + } + + @Override + public TargetsSection merge(TargetsSection otherSection) { + List mergedIncludedTargets = + ListUtils.concat(includedTargets, otherSection.includedTargets); + List mergedExcludedTargets = + ListUtils.concat(excludedTargets, otherSection.excludedTargets); + + return new TargetsSection(mergedIncludedTargets, mergedExcludedTargets); + } + + public List getIncludedTargets() { + return includedTargets; + } + + public List getExcludedTargets() { + return excludedTargets; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TargetsSection that = (TargetsSection) o; + return Objects.equals(includedTargets, that.includedTargets) + && Objects.equals(excludedTargets, that.excludedTargets); + } + + @Override + public int hashCode() { + return Objects.hash(includedTargets, excludedTargets); + } + + @Override + public String toString() { + return "TargetsSection{" + + "includedTargets=" + + includedTargets + + ", excludedTargets=" + + excludedTargets + + '}'; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/BUILD b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/BUILD new file mode 100644 index 000000000..cb4b81e28 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/BUILD @@ -0,0 +1,20 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "parser", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter", + "@maven//:com_google_guava_guava", + "@maven//:io_vavr_vavr", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewDefaultParserProvider.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewDefaultParserProvider.java new file mode 100644 index 000000000..8d051990e --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewDefaultParserProvider.java @@ -0,0 +1,28 @@ +package org.jetbrains.bsp.bazel.projectview.parser; + +import io.vavr.control.Try; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; +import org.jetbrains.bsp.bazel.projectview.model.ProjectViewDefaultProvider; +import org.jetbrains.bsp.bazel.projectview.model.ProjectViewProvider; + +public class ProjectViewDefaultParserProvider implements ProjectViewProvider { + + private static final ProjectViewParser PARSER = new ProjectViewParserImpl(); + private static final Path PROJECT_VIEW_FILE = Paths.get(Constants.DEFAULT_PROJECT_VIEW_FILE); + private static final ProjectViewProvider PROJECT_VIEW_PROVIDER = new ProjectViewDefaultProvider(); + + @Override + public ProjectView create() { + return Try.success(PROJECT_VIEW_FILE) + .filter(this::doesProjectViewFileExists) + .mapTry(PARSER::parse) + .getOrElse(PROJECT_VIEW_PROVIDER::create); + } + + private boolean doesProjectViewFileExists(Path projectViewFile) { + return projectViewFile.toFile().isFile(); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewImportParser.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewImportParser.java new file mode 100644 index 000000000..eea665e56 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewImportParser.java @@ -0,0 +1,49 @@ +package org.jetbrains.bsp.bazel.projectview.parser; + +import io.vavr.control.Try; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; +import org.jetbrains.bsp.bazel.projectview.parser.splitter.ProjectViewRawSection; + +public class ProjectViewImportParser { + + private static final Logger LOGGER = LogManager.getLogger(ProjectViewImportParser.class); + + private static final String IMPORT_SECTION_HEADER = "import"; + + private final ProjectViewParser projectViewParser; + + public ProjectViewImportParser(ProjectViewParser projectViewParser) { + this.projectViewParser = projectViewParser; + } + + public Optional parseRawSections(List rawSections) { + return rawSections.stream() + .filter(this::isImportSection) + .findFirst() + .map(this::getProjectViewFile) + .flatMap(this::parseFile); + } + + private boolean isImportSection(ProjectViewRawSection rawSection) { + return rawSection.getSectionHeader().equals(IMPORT_SECTION_HEADER); + } + + private Path getProjectViewFile(ProjectViewRawSection rawSection) { + String projectViewFilePath = rawSection.getSectionBody().trim(); + + return Paths.get(projectViewFilePath); + } + + private Optional parseFile(Path projectViewPath) { + return Try.success(projectViewPath) + .mapTry(projectViewParser::parse) + .onFailure(LOGGER::error) + .toJavaOptional(); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParser.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParser.java new file mode 100644 index 000000000..d150d0568 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParser.java @@ -0,0 +1,25 @@ +package org.jetbrains.bsp.bazel.projectview.parser; + +import com.google.common.io.Files; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; + +/** + * Project view file parser. Its purpose is to parse *.bazelproject file and create ProjectView + * + * @see org.jetbrains.bsp.bazel.projectview.model.ProjectView + */ +public interface ProjectViewParser { + + ProjectView parse(String projectViewFileContent); + + default ProjectView parse(Path projectViewPath) throws IOException { + File projectViewFile = projectViewPath.toFile(); + String fileContent = Files.asCharSource(projectViewFile, Charset.defaultCharset()).read(); + + return parse(fileContent); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParserImpl.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParserImpl.java new file mode 100644 index 000000000..fc7ad458f --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParserImpl.java @@ -0,0 +1,69 @@ +package org.jetbrains.bsp.bazel.projectview.parser; + +import java.util.List; +import java.util.Optional; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.DirectoriesSection; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.TargetsSection; +import org.jetbrains.bsp.bazel.projectview.parser.sections.specific.DirectoriesSectionParser; +import org.jetbrains.bsp.bazel.projectview.parser.sections.specific.TargetsSectionParser; +import org.jetbrains.bsp.bazel.projectview.parser.splitter.ProjectViewRawSection; +import org.jetbrains.bsp.bazel.projectview.parser.splitter.ProjectViewSectionSplitter; + +/** + * Our default implementation of ProjectViewParser + * + *

Logic schema: + * + *

1. extracting blocks:

: + * - - + * + *

or:

:
+ * + *

2. looping through extracted and checking which block could be parsed by the given section + * parser + * + *

3. applying section specific parser to the chosen section + * + * @see org.jetbrains.bsp.bazel.projectview.parser.ProjectViewParser + * @see org.jetbrains.bsp.bazel.projectview.parser.splitter.ProjectViewSectionSplitter + * @see org.jetbrains.bsp.bazel.projectview.parser.sections.ProjectViewSectionParser + */ +class ProjectViewParserImpl implements ProjectViewParser { + + private static final ProjectViewRawSectionParser DIRECTORY_PARSER = + ProjectViewRawSectionParser.forParser(new DirectoriesSectionParser()); + + private static final ProjectViewRawSectionParser TARGETS_PARSER = + ProjectViewRawSectionParser.forParser(new TargetsSectionParser()); + + private final ProjectViewImportParser projectViewImportParser; + + public ProjectViewParserImpl() { + this.projectViewImportParser = new ProjectViewImportParser(this); + } + + @Override + public ProjectView parse(String projectViewFileContent) { + List rawSections = + ProjectViewSectionSplitter.split(projectViewFileContent); + + ProjectView projectView = buildFile(rawSections); + Optional importedProjectView = + projectViewImportParser.parseRawSections(rawSections); + + return mergeProjectViewIfNeeded(projectView, importedProjectView); + } + + private ProjectView buildFile(List rawSections) { + return ProjectView.builder() + .directories(DIRECTORY_PARSER.parseRawSections(rawSections)) + .targets(TARGETS_PARSER.parseRawSections(rawSections)) + .build(); + } + + private ProjectView mergeProjectViewIfNeeded( + ProjectView projectView, Optional importedProjectView) { + return importedProjectView.map(imported -> imported.merge(projectView)).orElse(projectView); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewRawSectionParser.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewRawSectionParser.java new file mode 100644 index 000000000..baa7124b6 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewRawSectionParser.java @@ -0,0 +1,39 @@ +package org.jetbrains.bsp.bazel.projectview.parser; + +import java.util.List; +import java.util.Optional; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSection; +import org.jetbrains.bsp.bazel.projectview.parser.sections.ProjectViewSectionParser; +import org.jetbrains.bsp.bazel.projectview.parser.splitter.ProjectViewRawSection; + +public class ProjectViewRawSectionParser { + + private final ProjectViewSectionParser parser; + + private ProjectViewRawSectionParser(ProjectViewSectionParser parser) { + this.parser = parser; + } + + public static ProjectViewRawSectionParser forParser( + ProjectViewSectionParser parser) { + return new ProjectViewRawSectionParser<>(parser); + } + + public T parseRawSections(List rawSections) { + return parseOptionalRawSections(rawSections) + .orElseThrow( + () -> + new IllegalArgumentException(this.parser.sectionName() + " section is required!")); + } + + public Optional parseOptionalRawSections(List rawSections) { + return rawSections.stream() + .filter(this::isSectionParsable) + .findFirst() + .map(section -> parser.parse(section.getSectionBody())); + } + + private boolean isSectionParsable(ProjectViewRawSection rawSection) { + return parser.isSectionParsable(rawSection.getSectionHeader()); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/BUILD b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/BUILD new file mode 100644 index 000000000..5750aeead --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/BUILD @@ -0,0 +1,14 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "sections", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific", + "@maven//:com_google_guava_guava", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/ProjectViewListSectionParser.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/ProjectViewListSectionParser.java new file mode 100644 index 000000000..b757132c6 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/ProjectViewListSectionParser.java @@ -0,0 +1,57 @@ +package org.jetbrains.bsp.bazel.projectview.parser.sections; + +import java.util.List; +import java.util.stream.Collectors; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSection; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSectionHeader; + +public abstract class ProjectViewListSectionParser + extends ProjectViewSectionParser { + + private static final String EXCLUDED_ENTRY_PREFIX = "-"; + + private final boolean exclusionary; + + protected ProjectViewListSectionParser( + ProjectViewSectionHeader sectionHeader, boolean exclusionary) { + super(sectionHeader); + this.exclusionary = exclusionary; + } + + protected List parseIncludedEntries(String sectionBody) { + List entries = splitSectionEntries(sectionBody); + List includedEntries = getIncludedEntries(entries); + + return getValueIfExclusionaryOrElse(includedEntries, entries); + } + + private List getIncludedEntries(List entries) { + return entries.stream().filter(entry -> !isExcluded(entry)).collect(Collectors.toList()); + } + + protected List parseExcludedEntries(String sectionBody) { + List entries = splitSectionEntries(sectionBody); + List excludedEntries = getExcludedEntries(entries); + + return getValueIfExclusionaryOrElse(excludedEntries, entries); + } + + private List getExcludedEntries(List entries) { + return entries.stream() + .filter(this::isExcluded) + .map(entry -> entry.substring(1)) + .collect(Collectors.toList()); + } + + private List getValueIfExclusionaryOrElse(List value, List elseValue) { + if (exclusionary) { + return value; + } else { + return elseValue; + } + } + + private boolean isExcluded(String entry) { + return entry.startsWith(EXCLUDED_ENTRY_PREFIX); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/ProjectViewSectionParser.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/ProjectViewSectionParser.java new file mode 100644 index 000000000..0907ae44c --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/ProjectViewSectionParser.java @@ -0,0 +1,36 @@ +package org.jetbrains.bsp.bazel.projectview.parser.sections; + +import com.google.common.base.Splitter; +import java.util.List; +import java.util.regex.Pattern; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSection; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSectionHeader; + +/** + * Project view file section parser, implementation should parse raw string body into given section + * class + * + * @param project view section which can be parsed with implementation + */ +public abstract class ProjectViewSectionParser { + + private final ProjectViewSectionHeader sectionHeader; + + protected ProjectViewSectionParser(ProjectViewSectionHeader sectionHeader) { + this.sectionHeader = sectionHeader; + } + + public abstract T parse(String sectionBody); + + public boolean isSectionParsable(String sectionHeader) { + return sectionHeader.equals(this.sectionHeader.toString()); + } + + public String sectionName() { + return sectionHeader.toString(); + } + + protected List splitSectionEntries(String sectionBody) { + return Splitter.on(Pattern.compile("[ \n\t]+")).omitEmptyStrings().splitToList(sectionBody); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/BUILD b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/BUILD new file mode 100644 index 000000000..75e9c7339 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/BUILD @@ -0,0 +1,13 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "specific", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections", + "@maven//:com_google_guava_guava", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/DirectoriesSectionParser.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/DirectoriesSectionParser.java new file mode 100644 index 000000000..31a5e80ed --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/DirectoriesSectionParser.java @@ -0,0 +1,31 @@ +package org.jetbrains.bsp.bazel.projectview.parser.sections.specific; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSectionHeader; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.DirectoriesSection; +import org.jetbrains.bsp.bazel.projectview.parser.sections.ProjectViewListSectionParser; + +public class DirectoriesSectionParser extends ProjectViewListSectionParser { + + public DirectoriesSectionParser() { + super(ProjectViewSectionHeader.DIRECTORIES, true); + } + + @Override + public DirectoriesSection parse(String sectionBody) { + List includedEntries = parseIncludedEntries(sectionBody); + List includedPaths = mapEntriesToPaths(includedEntries); + + List excludedEntries = parseExcludedEntries(sectionBody); + List excludedPaths = mapEntriesToPaths(excludedEntries); + + return new DirectoriesSection(includedPaths, excludedPaths); + } + + private List mapEntriesToPaths(List entries) { + return entries.stream().map(Paths::get).collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/TargetsSectionParser.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/TargetsSectionParser.java new file mode 100644 index 000000000..42aeda1ef --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/TargetsSectionParser.java @@ -0,0 +1,21 @@ +package org.jetbrains.bsp.bazel.projectview.parser.sections.specific; + +import java.util.List; +import org.jetbrains.bsp.bazel.projectview.model.sections.ProjectViewSectionHeader; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.TargetsSection; +import org.jetbrains.bsp.bazel.projectview.parser.sections.ProjectViewListSectionParser; + +public class TargetsSectionParser extends ProjectViewListSectionParser { + + public TargetsSectionParser() { + super(ProjectViewSectionHeader.TARGETS, true); + } + + @Override + public TargetsSection parse(String sectionBody) { + List includedEntries = parseIncludedEntries(sectionBody); + List excludedEntries = parseExcludedEntries(sectionBody); + + return new TargetsSection(includedEntries, excludedEntries); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/BUILD b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/BUILD new file mode 100644 index 000000000..fca8331db --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/BUILD @@ -0,0 +1,10 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "splitter", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "@maven//:com_google_guava_guava", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewRawSection.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewRawSection.java new file mode 100644 index 000000000..3213df615 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewRawSection.java @@ -0,0 +1,52 @@ +package org.jetbrains.bsp.bazel.projectview.parser.splitter; + +import java.util.Objects; + +public class ProjectViewRawSection { + + private final String sectionHeader; + private final String sectionBody; + + public ProjectViewRawSection(String sectionHeader, String sectionBody) { + this.sectionHeader = sectionHeader; + this.sectionBody = sectionBody; + } + + public String getSectionHeader() { + return sectionHeader; + } + + public String getSectionBody() { + return sectionBody; + } + + @Override + public String toString() { + return "ProjectViewRawSection{" + + "sectionHeader='" + + sectionHeader + + '\'' + + ", sectionBody='" + + sectionBody + + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProjectViewRawSection that = (ProjectViewRawSection) o; + return Objects.equals(sectionHeader, that.sectionHeader) + && Objects.equals(sectionBody, that.sectionBody); + } + + @Override + public int hashCode() { + return Objects.hash(sectionHeader, sectionBody); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewSectionHeaderPosition.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewSectionHeaderPosition.java new file mode 100644 index 000000000..1a912cc85 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewSectionHeaderPosition.java @@ -0,0 +1,27 @@ +package org.jetbrains.bsp.bazel.projectview.parser.splitter; + +class ProjectViewSectionHeaderPosition { + + private final int startIndex; + private final int endIndex; + + private final String header; + + ProjectViewSectionHeaderPosition(int startIndex, int endIndex, String header) { + this.startIndex = startIndex; + this.endIndex = endIndex; + this.header = header; + } + + int getStartIndex() { + return startIndex; + } + + int getEndIndex() { + return endIndex; + } + + String getHeader() { + return header; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewSectionSplitter.java b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewSectionSplitter.java new file mode 100644 index 000000000..9681b5cd6 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewSectionSplitter.java @@ -0,0 +1,91 @@ +package org.jetbrains.bsp.bazel.projectview.parser.splitter; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class ProjectViewSectionSplitter { + + private static final String SECTION_HEADER_REGEX = "(^[a-z_]+[:]?)"; + private static final String POSSIBLE_HEADER_ENDING_CHARACTER = ":"; + + public static List split(String fileContent) { + List sectionsHeadersPositions = + splitIntoSectionsHeadersPositions(fileContent); + + return parseRawSections(fileContent, sectionsHeadersPositions); + } + + private static List splitIntoSectionsHeadersPositions( + String fileContent) { + Pattern pattern = Pattern.compile(SECTION_HEADER_REGEX, Pattern.MULTILINE); + Matcher matcher = pattern.matcher(fileContent); + + return getStartingAndEndingIndexes(matcher); + } + + private static List getStartingAndEndingIndexes( + Matcher matcher) { + ArrayList result = new ArrayList<>(); + + while (matcher.find()) { + ProjectViewSectionHeaderPosition position = + new ProjectViewSectionHeaderPosition(matcher.start(), matcher.end(), matcher.group()); + + result.add(position); + } + + return result; + } + + private static List parseRawSections( + String fileContent, List sectionsHeadersPositions) { + ListIterator iterator = + sectionsHeadersPositions.listIterator(); + List result = new ArrayList<>(); + + while (iterator.hasNext()) { + ProjectViewSectionHeaderPosition currentPosition = iterator.next(); + Optional nextPosition = getNextPosition(iterator); + + ProjectViewRawSection rawSection = + parseProjectViewRawSection(currentPosition, nextPosition, fileContent); + result.add(rawSection); + } + + return result; + } + + private static Optional getNextPosition( + ListIterator iterator) { + if (iterator.hasNext()) { + ProjectViewSectionHeaderPosition nextPosition = iterator.next(); + iterator.previous(); + return Optional.of(nextPosition); + } + + return Optional.empty(); + } + + private static ProjectViewRawSection parseProjectViewRawSection( + ProjectViewSectionHeaderPosition currentPosition, + Optional nextPosition, + String fileContent) { + + String sectionBody = + nextPosition + .map(ProjectViewSectionHeaderPosition::getStartIndex) + .map(startIndex -> fileContent.substring(currentPosition.getEndIndex(), startIndex)) + .orElse(fileContent.substring(currentPosition.getEndIndex())); + String sectionHeader = removeEndingHeaderColonAndSpaces(currentPosition.getHeader()); + + return new ProjectViewRawSection(sectionHeader, sectionBody); + } + + private static String removeEndingHeaderColonAndSpaces(String header) { + return header.replace(POSSIBLE_HEADER_ENDING_CHARACTER, "").trim(); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/BUILD new file mode 100644 index 000000000..1ae79a05d --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/BUILD @@ -0,0 +1,38 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +java_library( + name = "server", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/data", + "//src/main/java/org/jetbrains/bsp/bazel/server/bep", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/config", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/services", + "//src/main/java/org/jetbrains/bsp/bazel/server/loggers", + "@io_bazel//src/main/protobuf:build_java_proto", + "@io_bazel//third_party/grpc:grpc-jar", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_code_gson_gson", + "@maven//:com_google_guava_guava", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", + "@maven//:org_eclipse_xtext_org_eclipse_xtext_xbase_lib", + ], +) + +java_binary( + name = "bsp-run", + main_class = "org.jetbrains.bsp.bazel.server.ServerInitializer", + visibility = ["//visibility:public"], + runtime_deps = ["//src/main/java/org/jetbrains/bsp/bazel/server"], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java b/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java new file mode 100644 index 000000000..90f73eb7d --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/BazelBspServer.java @@ -0,0 +1,133 @@ +package org.jetbrains.bsp.bazel.server; + +import ch.epfl.scala.bsp4j.BuildClient; +import ch.epfl.scala.bsp4j.BuildServer; +import ch.epfl.scala.bsp4j.CppBuildServer; +import ch.epfl.scala.bsp4j.JavaBuildServer; +import ch.epfl.scala.bsp4j.ScalaBuildServer; +import io.grpc.ServerBuilder; +import org.eclipse.lsp4j.jsonrpc.Launcher; +import org.jetbrains.bsp.bazel.server.bazel.BazelDataResolver; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bep.BepServer; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerBuildManager; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerLifetime; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerRequestHelpers; +import org.jetbrains.bsp.bazel.server.bsp.BspImplementationHub; +import org.jetbrains.bsp.bazel.server.bsp.BspIntegrationData; +import org.jetbrains.bsp.bazel.server.bsp.config.BazelBspServerConfig; +import org.jetbrains.bsp.bazel.server.bsp.impl.BuildServerImpl; +import org.jetbrains.bsp.bazel.server.bsp.impl.CppBuildServerImpl; +import org.jetbrains.bsp.bazel.server.bsp.impl.JavaBuildServerImpl; +import org.jetbrains.bsp.bazel.server.bsp.impl.ScalaBuildServerImpl; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspAspectsManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspCompilationManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspQueryManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspTargetManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelCppTargetManager; +import org.jetbrains.bsp.bazel.server.bsp.services.BuildServerService; +import org.jetbrains.bsp.bazel.server.bsp.services.CppBuildServerService; +import org.jetbrains.bsp.bazel.server.bsp.services.JavaBuildServerService; +import org.jetbrains.bsp.bazel.server.bsp.services.ScalaBuildServerService; +import org.jetbrains.bsp.bazel.server.loggers.BuildClientLogger; + +public class BazelBspServer { + + private final BazelBspServerConfig bazelBspServerConfig; + private final BazelRunner bazelRunner; + private final BazelData bazelData; + + private BspImplementationHub bspImplementationHub; + private BazelBspServerBuildManager serverBuildManager; + + public BazelBspServer(BazelBspServerConfig bazelBspServerConfig) { + this.bazelBspServerConfig = bazelBspServerConfig; + this.bazelRunner = new BazelRunner(bazelBspServerConfig.getBazelPath()); + BazelDataResolver bazelDataResolver = new BazelDataResolver(bazelRunner); + this.bazelData = bazelDataResolver.resolveBazelData(); + } + + public void startServer(BspIntegrationData bspIntegrationData) { + BazelBspServerLifetime serverLifetime = new BazelBspServerLifetime(); + BazelBspServerRequestHelpers serverRequestHelpers = + new BazelBspServerRequestHelpers(serverLifetime); + + BazelBspCompilationManager bazelBspCompilationManager = + new BazelBspCompilationManager(bazelRunner, bazelData); + BazelBspAspectsManager bazelBspAspectsManager = + new BazelBspAspectsManager(bazelBspCompilationManager, bazelRunner); + BazelCppTargetManager bazelCppTargetManager = new BazelCppTargetManager(bazelBspAspectsManager); + BazelBspTargetManager bazelBspTargetManager = + new BazelBspTargetManager(bazelRunner, bazelBspAspectsManager, bazelCppTargetManager); + BazelBspQueryManager bazelBspQueryManager = + new BazelBspQueryManager( + bazelBspServerConfig.getProjectView(), bazelData, bazelRunner, bazelBspTargetManager); + + this.serverBuildManager = + new BazelBspServerBuildManager( + serverRequestHelpers, + bazelData, + bazelRunner, + bazelBspCompilationManager, + bazelBspAspectsManager, + bazelBspTargetManager, + bazelCppTargetManager, + bazelBspQueryManager); + + BuildServerService buildServerService = + new BuildServerService( + serverRequestHelpers, + serverLifetime, + serverBuildManager, + bazelData, + bazelRunner, + bazelBspServerConfig.getProjectView()); + + ScalaBuildServerService scalaBuildServerService = + new ScalaBuildServerService( + bazelData, bazelRunner, bazelBspCompilationManager, bazelBspQueryManager); + JavaBuildServerService javaBuildServerService = + new JavaBuildServerService( + bazelBspCompilationManager, bazelBspQueryManager, bazelData, bazelRunner); + CppBuildServerService cppBuildServerService = new CppBuildServerService(bazelRunner); + + ScalaBuildServer scalaBuildServer = + new ScalaBuildServerImpl(scalaBuildServerService, serverRequestHelpers); + JavaBuildServer javaBuildServer = + new JavaBuildServerImpl(javaBuildServerService, serverRequestHelpers); + CppBuildServer cppBuildServer = + new CppBuildServerImpl(cppBuildServerService, serverRequestHelpers); + BuildServer buildServer = new BuildServerImpl(buildServerService, serverRequestHelpers); + + this.bspImplementationHub = + new BspImplementationHub(buildServer, scalaBuildServer, javaBuildServer, cppBuildServer); + + integrateBsp(bspIntegrationData); + } + + private void integrateBsp(BspIntegrationData bspIntegrationData) { + Launcher launcher = + new Launcher.Builder() + .traceMessages(bspIntegrationData.getTraceWriter()) + .setOutput(bspIntegrationData.getStdout()) + .setInput(bspIntegrationData.getStdin()) + .setLocalService(bspImplementationHub) + .setRemoteInterface(BuildClient.class) + .setExecutorService(bspIntegrationData.getExecutor()) + .create(); + + bspIntegrationData.setLauncher(launcher); + BuildClientLogger buildClientLogger = new BuildClientLogger(launcher.getRemoteProxy()); + + BepServer bepServer = new BepServer(bazelData, launcher.getRemoteProxy(), buildClientLogger); + serverBuildManager.setBepServer(bepServer); + bazelRunner.setLogger(buildClientLogger); + + bspIntegrationData.setServer(ServerBuilder.forPort(0).addService(bepServer).build()); + } + + public void setBesBackendPort(int port) { + bazelRunner.setBesBackendPort(port); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/ServerInitializer.java b/src/main/java/org/jetbrains/bsp/bazel/server/ServerInitializer.java new file mode 100644 index 000000000..74a09f9f6 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/ServerInitializer.java @@ -0,0 +1,88 @@ +package org.jetbrains.bsp.bazel.server; + +import io.grpc.Server; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; +import org.jetbrains.bsp.bazel.projectview.model.ProjectViewProvider; +import org.jetbrains.bsp.bazel.projectview.parser.ProjectViewDefaultParserProvider; +import org.jetbrains.bsp.bazel.server.bsp.BspIntegrationData; +import org.jetbrains.bsp.bazel.server.bsp.config.BazelBspServerConfig; +import org.jetbrains.bsp.bazel.server.bsp.config.ServerArgsProjectViewProvider; + +public class ServerInitializer { + + public static void main(String[] args) { + if (args.length == 0) { + System.err.printf("Expected path to bazel; got args: %s%n", Arrays.toString(args)); + System.exit(1); + } + + boolean hasErrors = false; + PrintStream stdout = System.out; + InputStream stdin = System.in; + ExecutorService executor = Executors.newCachedThreadPool(); + + try { + Path rootDir = Paths.get(Constants.BAZELBSP_DIR_NAME).toAbsolutePath(); + Files.createDirectories(rootDir); + + Path traceFile = rootDir.resolve(Constants.BAZELBSP_TRACE_JSON_FILE_NAME); + PrintWriter traceWriter = + new PrintWriter( + Files.newOutputStream( + traceFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)); + + BazelBspServerConfig bazelBspServerConfig = getBazelBspServerConfig(args); + + BspIntegrationData bspIntegrationData = + new BspIntegrationData(stdout, stdin, executor, traceWriter); + BazelBspServer bspServer = new BazelBspServer(bazelBspServerConfig); + bspServer.startServer(bspIntegrationData); + + Server server = bspIntegrationData.getServer().start(); + bspServer.setBesBackendPort(server.getPort()); + + bspIntegrationData.getLauncher().startListening(); + server.awaitTermination(); + } catch (Exception e) { + e.printStackTrace(); + hasErrors = true; + } finally { + executor.shutdown(); + } + + if (hasErrors) { + System.exit(1); + } + } + + private static BazelBspServerConfig getBazelBspServerConfig(String[] args) { + String pathToBazel = args[0]; + ProjectView projectView = getProjectView(args); + return new BazelBspServerConfig(pathToBazel, projectView); + } + + private static ProjectView getProjectView(String[] args) { + ProjectViewProvider provider = getProjectViewProvider(args); + + return provider.create(); + } + + private static ProjectViewProvider getProjectViewProvider(String[] args) { + if (args.length == 2) { + return new ServerArgsProjectViewProvider(args[1]); + } + + return new ProjectViewDefaultParserProvider(); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BUILD new file mode 100644 index 000000000..ea5483ad1 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BUILD @@ -0,0 +1,22 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "bazel", + srcs = glob(["*.java"]), + resources = ["//src/main/resources:bsp-main-resources"], + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/data", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/params", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils", + "//src/main/java/org/jetbrains/bsp/bazel/server/loggers", + "@io_bazel//src/main/protobuf:analysis_java_proto", + "@io_bazel//src/main/protobuf:build_java_proto", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_guava_guava", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelDataResolver.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelDataResolver.java new file mode 100644 index 000000000..2fab4708a --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelDataResolver.java @@ -0,0 +1,45 @@ +package org.jetbrains.bsp.bazel.server.bazel; + +import com.google.common.collect.Iterables; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelProcessResult; + +public class BazelDataResolver { + + private static final String EXECUTION_ROOT_PARAMETER = "execution_root"; + private static final String WORKPLACE_ROOT_PARAMETER = "workspace"; + private static final String BAZEL_BIN_ROOT_PARAMETER = "bazel-bin"; + private static final String BAZEL_VERSION_PARAMETER = "release"; + + private final BazelRunner bazelRunner; + + public BazelDataResolver(BazelRunner bazelRunner) { + this.bazelRunner = bazelRunner; + } + + public BazelData resolveBazelData() { + String execRoot = readOnlyBazelLine(EXECUTION_ROOT_PARAMETER); + String workspaceRoot = readOnlyBazelLine(WORKPLACE_ROOT_PARAMETER); + String binRoot = readOnlyBazelLine(BAZEL_BIN_ROOT_PARAMETER); + String version = readOnlyBazelLine(BAZEL_VERSION_PARAMETER); + Path workspacePath = Paths.get(execRoot); + String workspaceLabel = workspacePath.toFile().getName(); + return new BazelData(execRoot, workspaceRoot, binRoot, workspaceLabel, version); + } + + private String readOnlyBazelLine(String argument) { + BazelProcessResult bazelProcessResult = + bazelRunner + .commandBuilder() + .info() + .withArgument(argument) + .executeBazelCommand() + .waitAndGetResult(); + List output = bazelProcessResult.getStdout(); + + return Iterables.getOnlyElement(output); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelProcess.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelProcess.java new file mode 100644 index 000000000..cd9a44797 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelProcess.java @@ -0,0 +1,61 @@ +package org.jetbrains.bsp.bazel.server.bazel; + +import java.io.InputStream; +import java.util.List; +import java.util.Optional; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelProcessResult; +import org.jetbrains.bsp.bazel.server.bazel.utils.BazelStreamReader; +import org.jetbrains.bsp.bazel.server.loggers.BuildClientLogger; + +public class BazelProcess { + + private static final int OK_EXIT_CODE = 0; + + private final Process process; + private final Optional buildClientLogger; + + BazelProcess(Process process, Optional buildClientLogger) { + this.process = process; + this.buildClientLogger = buildClientLogger; + } + + public BazelProcessResult waitAndGetResult() { + try { + int exitCode = process.waitFor(); + BazelProcessResult bazelProcessResult = + new BazelProcessResult(process.getInputStream(), process.getErrorStream(), exitCode); + + logBazelMessage(bazelProcessResult, exitCode); + + return bazelProcessResult; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private void logBazelMessage(BazelProcessResult bazelProcessResult, int exitCode) { + if (exitCode == OK_EXIT_CODE) { + logBazelMessage(bazelProcessResult); + } else { + logBazelError(bazelProcessResult); + } + } + + private void logBazelMessage(BazelProcessResult bazelProcessResult) { + String message = bazelProcessResult.getJoinedStderr(); + buildClientLogger.ifPresent(logger -> logger.logMessage(message)); + } + + private void logBazelError(BazelProcessResult bazelProcessResult) { + String error = bazelProcessResult.getJoinedStderr(); + buildClientLogger.ifPresent(logger -> logger.logError(error)); + } + + public InputStream getInputStream() { + return process.getInputStream(); + } + + public List getStderr() { + return BazelStreamReader.drainStream(process.getErrorStream()); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunner.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunner.java new file mode 100644 index 000000000..3d898c932 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunner.java @@ -0,0 +1,91 @@ +package org.jetbrains.bsp.bazel.server.bazel; + +import com.google.common.collect.Lists; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.bsp.bazel.server.loggers.BuildClientLogger; + +public class BazelRunner { + + private static final Logger LOGGER = LogManager.getLogger(BazelRunner.class); + + private static final String PUBLISH_ALL_ACTIONS = "--build_event_publish_all_actions"; + private static final String BES_BACKEND = "--bes_backend=grpc://localhost:"; + + private final String bazel; + + private Optional besBackendPort = Optional.empty(); + private Optional buildClientLogger = Optional.empty(); + + public BazelRunner(String bazelBinaryPath) { + this.bazel = bazelBinaryPath; + } + + public BazelRunnerCommandBuilder commandBuilder() { + return new BazelRunnerCommandBuilder(this); + } + + BazelProcess runBazelCommandBes(String command, List flags, List arguments) { + + List newFlags = getBesFlags(flags); + return runBazelCommand(command, newFlags, arguments); + } + + private List getBesFlags(List flags) { + List newFlags = Lists.newArrayList(getBesBackendAddress(), PUBLISH_ALL_ACTIONS); + newFlags.addAll(flags); + + return newFlags; + } + + private String getBesBackendAddress() { + Integer port = besBackendPort.orElseThrow(() -> new IllegalStateException("BES port not set")); + + return BES_BACKEND + port; + } + + BazelProcess runBazelCommand(String command, List flags, List arguments) { + if (arguments.isEmpty()) { + LOGGER.fatal("Not enough arguments"); + throw new IllegalArgumentException("Not enough arguments"); + } + + try { + LOGGER.info( + "Waiting for bazel - command: {}, flags: {}, arguments: {}", command, flags, arguments); + return runBazelProcess(command, flags, arguments); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private synchronized BazelProcess runBazelProcess( + String command, List flags, List arguments) throws IOException { + List processArgs = getProcessArgs(command, flags, arguments); + LOGGER.info("Running: {}", processArgs); + + ProcessBuilder processBuilder = new ProcessBuilder(processArgs); + Process process = processBuilder.start(); + + return new BazelProcess(process, buildClientLogger); + } + + private List getProcessArgs(String command, List flags, List arguments) { + List processArgs = Lists.newArrayList(bazel, command); + processArgs.addAll(flags); + processArgs.addAll(arguments); + + return processArgs; + } + + public void setBesBackendPort(int port) { + besBackendPort = Optional.of(port); + } + + public void setLogger(BuildClientLogger buildClientLogger) { + this.buildClientLogger = Optional.ofNullable(buildClientLogger); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunnerBuilder.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunnerBuilder.java new file mode 100644 index 000000000..a15e64f25 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunnerBuilder.java @@ -0,0 +1,98 @@ +package org.jetbrains.bsp.bazel.server.bazel; + +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelQueryKindParameters; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; +import org.jetbrains.bsp.bazel.server.bazel.utils.BazelArgumentsUtils; + +public class BazelRunnerBuilder { + + private final BazelRunner bazelRunner; + private final String bazelCommand; + + private final List flags; + private final List arguments; + + BazelRunnerBuilder(BazelRunner bazelRunner, String bazelCommand) { + this.bazelRunner = bazelRunner; + this.bazelCommand = bazelCommand; + this.flags = new ArrayList<>(); + this.arguments = new ArrayList<>(); + } + + public BazelRunnerBuilder withFlag(BazelRunnerFlag bazelFlag) { + flags.add(bazelFlag.toString()); + + return this; + } + + public BazelRunnerBuilder withFlag(BazelRunnerFlag bazelFlag, String value) { + flags.add(bazelFlag.toString()); + flags.add(value); + + return this; + } + + public BazelRunnerBuilder withFlags(List bazelFlags) { + flags.addAll(bazelFlags); + + return this; + } + + public BazelRunnerBuilder withArgument(String bazelArgument) { + arguments.add(bazelArgument); + + return this; + } + + public BazelRunnerBuilder withArguments(List bazelArguments) { + arguments.addAll(bazelArguments); + + return this; + } + + public BazelRunnerBuilder withTargets(List bazelTargets) { + String joinedTargets = BazelArgumentsUtils.getJoinedBazelTargets(bazelTargets); + arguments.add(joinedTargets); + + return this; + } + + public BazelRunnerBuilder withMnemonic(List bazelTargets, List languageIds) { + String argument = BazelArgumentsUtils.getMnemonicWithJoinedTargets(bazelTargets, languageIds); + arguments.add(argument); + + return this; + } + + public BazelRunnerBuilder withKind(BazelQueryKindParameters bazelParameter) { + return withKinds(ImmutableList.of(bazelParameter)); + } + + public BazelRunnerBuilder withKinds(List bazelParameters) { + String argument = BazelArgumentsUtils.getQueryKindForPatternsAndExpressions(bazelParameters); + arguments.add(argument); + + return this; + } + + public BazelRunnerBuilder withKindsAndExcept( + BazelQueryKindParameters parameters, String exception) { + String argument = + BazelArgumentsUtils.getQueryKindForPatternsAndExpressionsWithException( + ImmutableList.of(parameters), exception); + arguments.add(argument); + + return this; + } + + public BazelProcess executeBazelCommand() { + return bazelRunner.runBazelCommand(bazelCommand, flags, arguments); + } + + public BazelProcess executeBazelBesCommand() { + return bazelRunner.runBazelCommandBes(bazelCommand, flags, arguments); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunnerBuilderWithoutMnemonics.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunnerBuilderWithoutMnemonics.java new file mode 100644 index 000000000..71a8f6d10 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunnerBuilderWithoutMnemonics.java @@ -0,0 +1,16 @@ +package org.jetbrains.bsp.bazel.server.bazel; + +import java.util.List; +// Mnemonics are not accepted by some commands, such as the build command, therefore they must be +// treated as arguments. + +public class BazelRunnerBuilderWithoutMnemonics extends BazelRunnerBuilder { + public BazelRunnerBuilderWithoutMnemonics(BazelRunner bazelRunner, String bazelBuildCommand) { + super(bazelRunner, bazelBuildCommand); + } + + @Override + public BazelRunnerBuilder withTargets(List bazelTargets) { + return withArguments(bazelTargets); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunnerCommandBuilder.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunnerCommandBuilder.java new file mode 100644 index 000000000..b163b02c9 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/BazelRunnerCommandBuilder.java @@ -0,0 +1,51 @@ +package org.jetbrains.bsp.bazel.server.bazel; + +public class BazelRunnerCommandBuilder { + + private static final String BAZEL_AQUERY_COMMAND = "aquery"; + private static final String BAZEL_BUILD_COMMAND = "build"; + private static final String BAZEL_CLEAN_COMMAND = "clean"; + private static final String BAZEL_FETCH_COMMAND = "fetch"; + private static final String BAZEL_INFO_COMMAND = "info"; + private static final String BAZEL_QUERY_COMMAND = "query"; + private static final String BAZEL_RUN_COMMAND = "run"; + private static final String BAZEL_TEST_COMMAND = "test"; + + private final BazelRunner bazelRunner; + + BazelRunnerCommandBuilder(BazelRunner bazelRunner) { + this.bazelRunner = bazelRunner; + } + + public BazelRunnerBuilder aquery() { + return new BazelRunnerBuilder(bazelRunner, BAZEL_AQUERY_COMMAND); + } + + public BazelRunnerBuilder build() { + return new BazelRunnerBuilderWithoutMnemonics(bazelRunner, BAZEL_BUILD_COMMAND); + } + + public BazelRunnerBuilder clean() { + return new BazelRunnerBuilder(bazelRunner, BAZEL_CLEAN_COMMAND); + } + + public BazelRunnerBuilder fetch() { + return new BazelRunnerBuilder(bazelRunner, BAZEL_FETCH_COMMAND); + } + + public BazelRunnerBuilder info() { + return new BazelRunnerBuilder(bazelRunner, BAZEL_INFO_COMMAND); + } + + public BazelRunnerBuilder run() { + return new BazelRunnerBuilder(bazelRunner, BAZEL_RUN_COMMAND); + } + + public BazelRunnerBuilder query() { + return new BazelRunnerBuilder(bazelRunner, BAZEL_QUERY_COMMAND); + } + + public BazelRunnerBuilder test() { + return new BazelRunnerBuilder(bazelRunner, BAZEL_TEST_COMMAND); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/BUILD new file mode 100644 index 000000000..e3e45fb4d --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/BUILD @@ -0,0 +1,12 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "data", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils", + "//src/main/java/org/jetbrains/bsp/bazel/server/loggers", + "@maven//:ch_epfl_scala_bsp4j", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/BazelData.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/BazelData.java new file mode 100644 index 000000000..4430f6738 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/BazelData.java @@ -0,0 +1,43 @@ +package org.jetbrains.bsp.bazel.server.bazel.data; + +public class BazelData { + + private final String execRoot; + private final String workspaceRoot; + private final String binRoot; + private final String workspaceLabel; + private final SemanticVersion version; + + public BazelData( + String execRoot, + String workspaceRoot, + String binRoot, + String workspaceLabel, + String version) { + this.execRoot = execRoot; + this.workspaceRoot = workspaceRoot; + this.binRoot = binRoot; + this.workspaceLabel = workspaceLabel; + this.version = SemanticVersion.fromReleaseData(version); + } + + public String getExecRoot() { + return execRoot; + } + + public String getWorkspaceRoot() { + return workspaceRoot; + } + + public String getBinRoot() { + return binRoot; + } + + public String getWorkspaceLabel() { + return workspaceLabel; + } + + public SemanticVersion getVersion() { + return version; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/BazelProcessResult.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/BazelProcessResult.java new file mode 100644 index 000000000..d13caf401 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/BazelProcessResult.java @@ -0,0 +1,35 @@ +package org.jetbrains.bsp.bazel.server.bazel.data; + +import ch.epfl.scala.bsp4j.StatusCode; +import java.io.InputStream; +import java.util.List; +import org.jetbrains.bsp.bazel.server.bazel.utils.BazelStreamReader; +import org.jetbrains.bsp.bazel.server.bazel.utils.ExitCodeMapper; + +public class BazelProcessResult { + + private static final String LINES_DELIMITER = "\n"; + + private final InputStream stdout; + private final InputStream stderr; + private final int exitCode; + + public BazelProcessResult(InputStream stdout, InputStream stderr, int exitCode) { + this.stdout = stdout; + this.stderr = stderr; + this.exitCode = exitCode; + } + + public StatusCode getStatusCode() { + return ExitCodeMapper.mapExitCode(exitCode); + } + + public List getStdout() { + return BazelStreamReader.drainStream(stdout); + } + + public String getJoinedStderr() { + List lines = BazelStreamReader.drainStream(stderr); + return String.join(LINES_DELIMITER, lines); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/SemanticVersion.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/SemanticVersion.java new file mode 100644 index 000000000..8e3053247 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/data/SemanticVersion.java @@ -0,0 +1,82 @@ +package org.jetbrains.bsp.bazel.server.bazel.data; + +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SemanticVersion implements Comparable { + + private static final String SEMANTIC_VERSION_REGEX = + "^([0-9]+)\\.([0-9]+)\\.([0-9]+)[\\-a-zA-Z]*"; + private static final int MAJOR_VERSION_GROUP_ID = 1; + private static final int MINOR_VERSION_GROUP_ID = 2; + private static final int PATCH_VERSION_GROUP_ID = 3; + private static final Comparator comparator = + Comparator.nullsLast( + Comparator.comparing(SemanticVersion::getMajorVersion) + .thenComparing(SemanticVersion::getMinorVersion) + .thenComparing(SemanticVersion::getPatchVersion)); + + private final String version; + private final int majorVersion; + private final int minorVersion; + private final int patchVersion; + + public SemanticVersion(String version) { + if (version == null) { + throw new IllegalArgumentException("Version can't be null"); + } + + Pattern pattern = Pattern.compile(SEMANTIC_VERSION_REGEX); + Matcher matcher = pattern.matcher(version); + + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid version format"); + } + + this.version = version; + this.majorVersion = Integer.parseInt(matcher.group(MAJOR_VERSION_GROUP_ID)); + this.minorVersion = Integer.parseInt(matcher.group(MINOR_VERSION_GROUP_ID)); + this.patchVersion = Integer.parseInt(matcher.group(PATCH_VERSION_GROUP_ID)); + } + + public int getMajorVersion() { + return majorVersion; + } + + public int getMinorVersion() { + return minorVersion; + } + + public int getPatchVersion() { + return patchVersion; + } + + static SemanticVersion fromReleaseData(String version) { + return new SemanticVersion(version.split(" ")[1]); + } + + @Override + public int compareTo(SemanticVersion other) { + return comparator.compare(this, other); + } + + @Override + public int hashCode() { + return version.hashCode(); + } + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (that == null) { + return false; + } + if (this.getClass() != that.getClass()) { + return false; + } + return this.compareTo((SemanticVersion) that) == 0; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/params/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/params/BUILD new file mode 100644 index 000000000..d76135001 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/params/BUILD @@ -0,0 +1,10 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "params", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "@maven//:ch_epfl_scala_bsp4j", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/params/BazelQueryKindParameters.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/params/BazelQueryKindParameters.java new file mode 100644 index 000000000..9449cc60b --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/params/BazelQueryKindParameters.java @@ -0,0 +1,24 @@ +package org.jetbrains.bsp.bazel.server.bazel.params; + +public class BazelQueryKindParameters { + + private final String pattern; + private final String input; + + private BazelQueryKindParameters(String pattern, String input) { + this.pattern = pattern; + this.input = input; + } + + public static BazelQueryKindParameters fromPatternAndInput(String pattern, String input) { + return new BazelQueryKindParameters(pattern, input); + } + + public String getPattern() { + return pattern; + } + + public String getInput() { + return input; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/params/BazelRunnerFlag.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/params/BazelRunnerFlag.java new file mode 100644 index 000000000..3c82de906 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/params/BazelRunnerFlag.java @@ -0,0 +1,20 @@ +package org.jetbrains.bsp.bazel.server.bazel.params; + +public enum BazelRunnerFlag { + OUTPUT_PROTO("--output=proto"), + ASPECTS("--aspects"), + NOHOST_DEPS("--nohost_deps"), + NOIMPLICIT_DEPS("--noimplicit_deps"), + KEEP_GOING("--keep_going"), + NOBUILD("--nobuild"); + + private final String name; + + BazelRunnerFlag(String value) { + name = value; + } + + public String toString() { + return name; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/BUILD new file mode 100644 index 000000000..279c70777 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/BUILD @@ -0,0 +1,12 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "utils", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/params", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_guava_guava", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/BazelArgumentsUtils.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/BazelArgumentsUtils.java new file mode 100644 index 000000000..f8912ae54 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/BazelArgumentsUtils.java @@ -0,0 +1,60 @@ +package org.jetbrains.bsp.bazel.server.bazel.utils; + +import com.google.common.base.Joiner; +import java.util.List; +import java.util.stream.Collectors; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelQueryKindParameters; + +public final class BazelArgumentsUtils { + + private static final String JOIN_TARGETS_DELIMITER = " + "; + private static final String FUNCTIONS_DELIMITER = " union "; + + private static final String MNEMONIC_COMMAND = "mnemonic"; + private static final String KIND_COMMAND = "kind"; + private static final String EXCEPT_COMMAND = "except"; + + public static String getJoinedBazelTargets(List targets) { + String joinedTargets = joinBazelTargets(targets); + + return String.format("(%s)", joinedTargets); + } + + public static String getMnemonicWithJoinedTargets( + List targets, List languageIds) { + String joinedTargets = joinBazelTargets(targets); + + return getMnemonicsForTargets(joinedTargets, languageIds); + } + + private static String getMnemonicsForTargets(String targets, List languageIds) { + return languageIds.stream() + .map(languageId -> getMnemonicForLanguageAndTargets(languageId, targets)) + .collect(Collectors.joining(FUNCTIONS_DELIMITER)); + } + + public static String getQueryKindForPatternsAndExpressions( + List parameters) { + return parameters.stream() + .map(BazelArgumentsUtils::getQueryKind) + .collect(Collectors.joining(FUNCTIONS_DELIMITER)); + } + + public static String getQueryKindForPatternsAndExpressionsWithException( + List parameters, String exception) { + String kind = getQueryKindForPatternsAndExpressions(parameters); + return String.format("%s %s %s", kind, EXCEPT_COMMAND, exception); + } + + public static String getQueryKind(BazelQueryKindParameters parameter) { + return String.format("%s(%s, %s)", KIND_COMMAND, parameter.getPattern(), parameter.getInput()); + } + + private static String joinBazelTargets(List targets) { + return Joiner.on(JOIN_TARGETS_DELIMITER).join(targets); + } + + private static String getMnemonicForLanguageAndTargets(String languageId, String targets) { + return String.format("%s(%s, %s)", MNEMONIC_COMMAND, languageId, targets); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/BazelStreamReader.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/BazelStreamReader.java new file mode 100644 index 000000000..7745f71e2 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/BazelStreamReader.java @@ -0,0 +1,25 @@ +package org.jetbrains.bsp.bazel.server.bazel.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +public final class BazelStreamReader { + + public static List drainStream(InputStream stream) { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + List list = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + list.add(line.trim()); + } + return list; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/ExitCodeMapper.java b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/ExitCodeMapper.java new file mode 100644 index 000000000..8a942af22 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils/ExitCodeMapper.java @@ -0,0 +1,18 @@ +package org.jetbrains.bsp.bazel.server.bazel.utils; + +import ch.epfl.scala.bsp4j.StatusCode; +import com.google.common.collect.ImmutableMap; +import java.util.Map; + +public final class ExitCodeMapper { + + private static final Map EXIT_CODE_TO_STATUS_CODE = + ImmutableMap.of( + 0, StatusCode.OK, + 8, StatusCode.CANCELLED); + private static final StatusCode DEFAULT_STATUS_CODE = StatusCode.ERROR; + + public static StatusCode mapExitCode(int exitCode) { + return EXIT_CODE_TO_STATUS_CODE.getOrDefault(exitCode, DEFAULT_STATUS_CODE); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bep/BUILD new file mode 100644 index 000000000..9d80a3367 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/BUILD @@ -0,0 +1,28 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "bep", + srcs = glob(["*.java"]), + resources = ["//src/main/resources:bsp-main-resources"], + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/data", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils", + "//src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers", + "//src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error", + "//src/main/java/org/jetbrains/bsp/bazel/server/loggers", + "@com_google_protobuf//:protobuf_java", + "@googleapis//:google_devtools_build_v1_build_events_java_proto", + "@googleapis//:google_devtools_build_v1_publish_build_event_java_grpc", + "@googleapis//:google_devtools_build_v1_publish_build_event_java_proto", + "@googleapis//:google_devtools_build_v1_publish_build_event_proto", + "@io_bazel//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto", + "@io_bazel//third_party/grpc:grpc-jar", + "@io_bazel_rules_scala//src/protobuf/io/bazel/rules_scala:diagnostics_java_proto", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_guava_guava", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/BepDiagnosticsDispatcher.java b/src/main/java/org/jetbrains/bsp/bazel/server/bep/BepDiagnosticsDispatcher.java new file mode 100644 index 000000000..7916f37cb --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/BepDiagnosticsDispatcher.java @@ -0,0 +1,137 @@ +package org.jetbrains.bsp.bazel.server.bep; + +import ch.epfl.scala.bsp4j.BuildClient; +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.Diagnostic; +import ch.epfl.scala.bsp4j.DiagnosticSeverity; +import ch.epfl.scala.bsp4j.Position; +import ch.epfl.scala.bsp4j.PublishDiagnosticsParams; +import ch.epfl.scala.bsp4j.Range; +import ch.epfl.scala.bsp4j.SourceItem; +import ch.epfl.scala.bsp4j.TextDocumentIdentifier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import io.bazel.rules_scala.diagnostics.Diagnostics; +import io.bazel.rules_scala.diagnostics.Diagnostics.FileDiagnostics; +import io.bazel.rules_scala.diagnostics.Diagnostics.Severity; +import io.bazel.rules_scala.diagnostics.Diagnostics.TargetDiagnostics; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.bsp.bazel.commons.Uri; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; + +public class BepDiagnosticsDispatcher { + + private static final Logger LOGGER = LogManager.getLogger(BepDiagnosticsDispatcher.class); + + private static final Map CONVERTED_SEVERITY = + new ImmutableMap.Builder() + .put(Severity.UNKNOWN, DiagnosticSeverity.ERROR) + .put(Severity.ERROR, DiagnosticSeverity.ERROR) + .put(Severity.WARNING, DiagnosticSeverity.WARNING) + .put(Severity.INFORMATION, DiagnosticSeverity.INFORMATION) + .put(Severity.HINT, DiagnosticSeverity.HINT) + .build(); + + private final BazelData bazelData; + private final BuildClient bspClient; + + private final Map> buildTargetsSources = new HashMap<>(); + + public BepDiagnosticsDispatcher(BazelData bazelData, BuildClient bspClient) { + this.bazelData = bazelData; + this.bspClient = bspClient; + } + + public Map> collectDiagnostics( + BuildTargetIdentifier target, String diagnosticsLocation) { + try { + TargetDiagnostics targetDiagnostics = + TargetDiagnostics.parseFrom(Files.readAllBytes(Paths.get(diagnosticsLocation))); + + return targetDiagnostics.getDiagnosticsList().stream() + .peek(diagnostics -> LOGGER.debug("Collected diagnostics at: {}", diagnostics.getPath())) + .collect( + Collectors.toMap( + diagnostics -> getUriForPath(diagnostics.getPath()), + diagnostics -> convertDiagnostics(target, diagnostics))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void emitDiagnostics( + Map> filesToDiagnostics, BuildTargetIdentifier target) { + buildTargetsSources.getOrDefault(target, ImmutableList.of()).stream() + .map(source -> Uri.fromFileUri(source.getUri())) + .forEach(sourceUri -> addSourceAndPublish(sourceUri, filesToDiagnostics, target)); + } + + private void addSourceAndPublish( + Uri sourceUri, + Map> filesToDiagnostics, + BuildTargetIdentifier target) { + PublishDiagnosticsParams publishDiagnosticsParams = + new PublishDiagnosticsParams( + new TextDocumentIdentifier(sourceUri.toString()), target, ImmutableList.of(), true); + + filesToDiagnostics.putIfAbsent(sourceUri, Lists.newArrayList(publishDiagnosticsParams)); + + filesToDiagnostics.values().stream() + .flatMap(List::stream) + .forEach(bspClient::onBuildPublishDiagnostics); + } + + private List convertDiagnostics( + BuildTargetIdentifier target, FileDiagnostics request) { + List diagnostics = + request.getDiagnosticsList().stream() + .map(this::convertDiagnostic) + .collect(Collectors.toList()); + + PublishDiagnosticsParams publishDiagnosticsParams = + new PublishDiagnosticsParams( + new TextDocumentIdentifier(getUriForPath(request.getPath()).toString()), + target, + diagnostics, + true); + + return Lists.newArrayList(publishDiagnosticsParams); + } + + private Diagnostic convertDiagnostic(Diagnostics.Diagnostic diagProto) { + Optional severity = + Optional.ofNullable(CONVERTED_SEVERITY.get(diagProto.getSeverity())); + + Position startPosition = + new Position( + diagProto.getRange().getStart().getLine(), + diagProto.getRange().getStart().getCharacter()); + Position endPosition = + new Position( + diagProto.getRange().getEnd().getLine(), diagProto.getRange().getEnd().getCharacter()); + Range range = new Range(startPosition, endPosition); + + Diagnostic diagnostic = new Diagnostic(range, diagProto.getMessage()); + severity.ifPresent(diagnostic::setSeverity); + + return diagnostic; + } + + private Uri getUriForPath(String path) { + return Uri.fromExecOrWorkspacePath(path, bazelData.getExecRoot(), bazelData.getWorkspaceRoot()); + } + + public Map> getBuildTargetsSources() { + return buildTargetsSources; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/BepServer.java b/src/main/java/org/jetbrains/bsp/bazel/server/bep/BepServer.java new file mode 100644 index 000000000..b90dd8e34 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/BepServer.java @@ -0,0 +1,302 @@ +package org.jetbrains.bsp.bazel.server.bep; + +import ch.epfl.scala.bsp4j.BuildClient; +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.Diagnostic; +import ch.epfl.scala.bsp4j.PublishDiagnosticsParams; +import ch.epfl.scala.bsp4j.SourceItem; +import ch.epfl.scala.bsp4j.StatusCode; +import ch.epfl.scala.bsp4j.TaskFinishParams; +import ch.epfl.scala.bsp4j.TaskId; +import ch.epfl.scala.bsp4j.TaskStartParams; +import ch.epfl.scala.bsp4j.TextDocumentIdentifier; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.OutputGroup; +import com.google.devtools.build.v1.BuildEvent; +import com.google.devtools.build.v1.PublishBuildEventGrpc; +import com.google.devtools.build.v1.PublishBuildToolEventStreamRequest; +import com.google.devtools.build.v1.PublishBuildToolEventStreamResponse; +import com.google.devtools.build.v1.PublishLifecycleEventRequest; +import com.google.protobuf.Empty; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.commons.Uri; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bazel.utils.ExitCodeMapper; +import org.jetbrains.bsp.bazel.server.bep.parsers.ClasspathParser; +import org.jetbrains.bsp.bazel.server.bep.parsers.error.StderrDiagnosticsParser; +import org.jetbrains.bsp.bazel.server.loggers.BuildClientLogger; + +public class BepServer extends PublishBuildEventGrpc.PublishBuildEventImplBase { + + private static final Logger LOGGER = LogManager.getLogger(BepServer.class); + + private static final List EXCLUDED_PROGRESS_MESSAGES = + ImmutableList.of("Loading: 0 packages loaded"); + + private static final int URI_PREFIX_LENGTH = 7; + + private final BuildClient bspClient; + private final BuildClientLogger buildClientLogger; + + private final BazelData bazelData; + private final BepDiagnosticsDispatcher diagnosticsDispatcher; + + private final Deque startedEventTaskIds = new ArrayDeque<>(); + private final Map diagnosticsProtosLocations = new HashMap<>(); + private final Map> outputGroupPaths = new HashMap<>(); + private final Map namedSetsOfFiles = + new HashMap<>(); + + public BepServer( + BazelData bazelData, BuildClient bspClient, BuildClientLogger buildClientLogger) { + this.bazelData = bazelData; + this.bspClient = bspClient; + this.buildClientLogger = buildClientLogger; + this.diagnosticsDispatcher = new BepDiagnosticsDispatcher(bazelData, bspClient); + } + + @Override + public void publishLifecycleEvent( + PublishLifecycleEventRequest request, StreamObserver responseObserver) { + namedSetsOfFiles.clear(); + responseObserver.onNext(Empty.getDefaultInstance()); + responseObserver.onCompleted(); + } + + @Override + public StreamObserver publishBuildToolEventStream( + StreamObserver responseObserver) { + return new BepStreamObserver(this, responseObserver); + } + + public Map> collectDiagnostics( + BuildTargetIdentifier target, String diagnosticsLocation) { + return diagnosticsDispatcher.collectDiagnostics(target, diagnosticsLocation); + } + + public void emitDiagnostics( + Map> filesToDiagnostics, BuildTargetIdentifier target) { + diagnosticsDispatcher.emitDiagnostics(filesToDiagnostics, target); + } + + public void handleEvent(BuildEvent buildEvent) { + try { + BuildEventStreamProtos.BuildEvent event = + BuildEventStreamProtos.BuildEvent.parseFrom(buildEvent.getBazelEvent().getValue()); + + LOGGER.debug("Got event {}", event); + + processBuildStartedEvent(event); + processFinishedEvent(event); + fetchNamedSet(event); + processCompletedEvent(event); + processActionEvent(event); + processAbortedEvent(event); + processProgressEvent(event); + } catch (IOException e) { + LOGGER.error("Error deserializing BEP proto: {}", e.toString()); + } + } + + private void fetchNamedSet(BuildEventStreamProtos.BuildEvent event) { + if (event.getId().hasNamedSet()) { + namedSetsOfFiles.put(event.getId().getNamedSet().getId(), event.getNamedSetOfFiles()); + } + } + + private void processBuildStartedEvent(BuildEventStreamProtos.BuildEvent event) { + if (event.hasStarted() + && event.getStarted().getCommand().equals(Constants.BAZEL_BUILD_COMMAND)) { + consumeBuildStartedEvent(event.getStarted()); + } + } + + private void consumeBuildStartedEvent(BuildEventStreamProtos.BuildStarted buildStarted) { + TaskId taskId = new TaskId(buildStarted.getUuid()); + TaskStartParams startParams = new TaskStartParams(taskId); + startParams.setEventTime(buildStarted.getStartTimeMillis()); + + bspClient.onBuildTaskStart(startParams); + startedEventTaskIds.push(taskId); + } + + private void processFinishedEvent(BuildEventStreamProtos.BuildEvent event) { + if (event.hasFinished()) { + consumeFinishedEvent(event.getFinished()); + } + } + + private void consumeFinishedEvent(BuildEventStreamProtos.BuildFinished buildFinished) { + if (startedEventTaskIds.isEmpty()) { + LOGGER.debug("No start event id was found."); + return; + } + + if (startedEventTaskIds.size() > 1) { + LOGGER.debug("More than 1 start event was found"); + return; + } + + StatusCode exitCode = ExitCodeMapper.mapExitCode(buildFinished.getExitCode().getCode()); + TaskFinishParams finishParams = new TaskFinishParams(startedEventTaskIds.pop(), exitCode); + finishParams.setEventTime(buildFinished.getFinishTimeMillis()); + + bspClient.onBuildTaskFinish(finishParams); + } + + private void processCompletedEvent(BuildEventStreamProtos.BuildEvent event) { + if (event.hasCompleted()) { + consumeCompletedEvent(event.getCompleted()); + } + } + + private void consumeCompletedEvent(BuildEventStreamProtos.TargetComplete targetComplete) { + List outputGroups = targetComplete.getOutputGroupList(); + LOGGER.info("Consuming target completed event " + targetComplete); + if (outputGroups.size() == 1) { + OutputGroup outputGroup = outputGroups.get(0); + if (outputGroup.getName().equals(Constants.SCALA_COMPILER_CLASSPATH_FILES)) { + fetchScalaJars(outputGroup); + } + } + } + + private void fetchScalaJars(OutputGroup outputGroup) { + outputGroup.getFileSetsList().stream() + .flatMap(fileSetId -> namedSetsOfFiles.get(fileSetId.getId()).getFilesList().stream()) + .map(file -> URI.create(file.getUri())) + .flatMap(pathProtoUri -> ClasspathParser.fromAspect(pathProtoUri).stream()) + .peek(path -> LOGGER.info("Found path " + path)) + .forEach( + path -> + outputGroupPaths + .computeIfAbsent(outputGroup.getName(), key -> new HashSet<>()) + .add( + Uri.fromExecPath( + Constants.EXEC_ROOT_PREFIX + path, bazelData.getExecRoot()))); + } + + private void processActionEvent(BuildEventStreamProtos.BuildEvent event) { + if (event.hasAction()) { + consumeActionEvent(event.getAction()); + } + } + + private void consumeActionEvent(BuildEventStreamProtos.ActionExecuted action) { + if (!Constants.SUPPORTED_COMPILERS.contains(action.getType())) { + // Ignore file template writes and such. + // TODO(illicitonion): Maybe include them as task notifications (rather than diagnostics). + return; + } + + BuildTargetIdentifier target = new BuildTargetIdentifier(action.getLabel()); + + Map> filesToDiagnostics = + action.getActionMetadataLogsList().stream() + .filter(log -> log.getName().equals(Constants.DIAGNOSTICS)) + .peek(log -> LOGGER.info("Found diagnostics file in {}", log.getUri())) + .map( + log -> + diagnosticsDispatcher.collectDiagnostics( + target, log.getUri().substring(URI_PREFIX_LENGTH))) + .collect(HashMap::new, Map::putAll, Map::putAll); + + if (hasDiagnosticsOutput(target)) { + if (filesToDiagnostics.isEmpty()) { + filesToDiagnostics = + diagnosticsDispatcher.collectDiagnostics( + target, diagnosticsProtosLocations.get(target.getUri())); + } + diagnosticsProtosLocations.remove(target.getUri()); + } + + diagnosticsDispatcher.emitDiagnostics(filesToDiagnostics, target); + } + + private boolean hasDiagnosticsOutput(BuildTargetIdentifier target) { + return diagnosticsProtosLocations.containsKey(target.getUri()); + } + + private void processAbortedEvent(BuildEventStreamProtos.BuildEvent event) { + if (event.hasAborted()) { + consumeAbortedEvent(event.getAborted()); + } + } + + private void consumeAbortedEvent(BuildEventStreamProtos.Aborted aborted) { + if (aborted.getReason() != BuildEventStreamProtos.Aborted.AbortReason.NO_BUILD) { + String errorMessage = + String.format( + "Command aborted with reason %s: %s", aborted.getReason(), aborted.getDescription()); + buildClientLogger.logError(errorMessage); + + throw new RuntimeException(errorMessage); + } + } + + private void processProgressEvent(BuildEventStreamProtos.BuildEvent event) { + if (event.hasProgress()) { + consumeProgressEvent(event.getProgress()); + } + } + + private void consumeProgressEvent(BuildEventStreamProtos.Progress progress) { + Map> fileDiagnostics = + StderrDiagnosticsParser.parse(progress.getStderr()); + + fileDiagnostics.entrySet().stream() + .map(this::createParamsFromEntry) + .forEach(bspClient::onBuildPublishDiagnostics); + + logProgress(progress); + } + + private PublishDiagnosticsParams createParamsFromEntry( + Map.Entry> entry) { + String fileLocation = entry.getKey(); + List diagnostics = entry.getValue(); + + return new PublishDiagnosticsParams( + new TextDocumentIdentifier(Uri.fromAbsolutePath(fileLocation).toString()), + new BuildTargetIdentifier(""), + diagnostics, + false); + } + + private void logProgress(BuildEventStreamProtos.Progress progress) { + String progressMessage = progress.getStderr().trim(); + + if (shouldMessageBeLogged(progressMessage)) { + buildClientLogger.logMessage(progressMessage); + } + } + + private boolean shouldMessageBeLogged(String message) { + return EXCLUDED_PROGRESS_MESSAGES.stream().noneMatch(message::startsWith); + } + + public Map> getOutputGroupPaths() { + return outputGroupPaths; + } + + public Map getDiagnosticsProtosLocations() { + return diagnosticsProtosLocations; + } + + public Map> getBuildTargetsSources() { + return diagnosticsDispatcher.getBuildTargetsSources(); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/BepStreamObserver.java b/src/main/java/org/jetbrains/bsp/bazel/server/bep/BepStreamObserver.java new file mode 100644 index 000000000..a5779aee1 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/BepStreamObserver.java @@ -0,0 +1,58 @@ +package org.jetbrains.bsp.bazel.server.bep; + +import com.google.devtools.build.v1.PublishBuildToolEventStreamRequest; +import com.google.devtools.build.v1.PublishBuildToolEventStreamResponse; +import io.grpc.stub.StreamObserver; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class BepStreamObserver implements StreamObserver { + + private static final Logger LOGGER = LogManager.getLogger(BepStreamObserver.class); + + private static final String BUILD_EVENT_TYPE_URL = + "type.googleapis.com/build_event_stream.BuildEvent"; + + private final BepServer bepServer; + private final StreamObserver responseObserver; + + public BepStreamObserver( + BepServer bepServer, StreamObserver responseObserver) { + this.bepServer = bepServer; + this.responseObserver = responseObserver; + } + + @Override + public void onNext(PublishBuildToolEventStreamRequest request) { + if (isRequestBazelBuildEvent(request)) { + bepServer.handleEvent(request.getOrderedBuildEvent().getEvent()); + } + + PublishBuildToolEventStreamResponse response = + PublishBuildToolEventStreamResponse.newBuilder() + .setStreamId(request.getOrderedBuildEvent().getStreamId()) + .setSequenceNumber(request.getOrderedBuildEvent().getSequenceNumber()) + .build(); + + responseObserver.onNext(response); + } + + @Override + public void onError(Throwable throwable) { + LOGGER.info("Error from BEP stream: {}", throwable.toString()); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + + private boolean isRequestBazelBuildEvent(PublishBuildToolEventStreamRequest request) { + return request + .getOrderedBuildEvent() + .getEvent() + .getBazelEvent() + .getTypeUrl() + .equals(BUILD_EVENT_TYPE_URL); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/BUILD new file mode 100644 index 000000000..21965bae0 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/BUILD @@ -0,0 +1,11 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "parsers", + srcs = glob(["*.java"]), + resources = ["//src/main/resources:bsp-main-resources"], + visibility = ["//visibility:public"], + deps = [ + "@maven//:com_google_guava_guava", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/ClasspathParser.java b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/ClasspathParser.java new file mode 100644 index 000000000..54b9c1a20 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/ClasspathParser.java @@ -0,0 +1,45 @@ +package org.jetbrains.bsp.bazel.server.bep.parsers; + +import com.google.common.base.Splitter; +import com.google.common.io.Files; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.stream.Collectors; + +public final class ClasspathParser { + + private static final String ASPECTS_OUTPUT_FILE_LINE_DELIMITER = "\""; + private static final Integer ASPECTS_OUTPUT_FILE_CLASSPATH_VALID_PARTS_NUMBER = 3; + private static final Integer ASPECTS_OUTPUT_FILE_CLASSPATH_LINE_INDEX = 1; + + public static List fromAspect(URI dependenciesAspectOutput) { + try { + return fromAspectOrThrow(dependenciesAspectOutput); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static List fromAspectOrThrow(URI dependenciesAspectOutput) throws IOException { + File dependenciesAspectOutputFile = new File(dependenciesAspectOutput); + + return Files.readLines(dependenciesAspectOutputFile, StandardCharsets.UTF_8).stream() + .map(ClasspathParser::splitAspectsOutputFileLine) + .peek(ClasspathParser::throwExceptionIfLineHasWrongFormat) + .map(parts -> parts.get(ASPECTS_OUTPUT_FILE_CLASSPATH_LINE_INDEX)) + .collect(Collectors.toList()); + } + + private static List splitAspectsOutputFileLine(String line) { + return Splitter.on(ASPECTS_OUTPUT_FILE_LINE_DELIMITER).splitToList(line); + } + + private static void throwExceptionIfLineHasWrongFormat(List lineParts) { + if (lineParts.size() != ASPECTS_OUTPUT_FILE_CLASSPATH_VALID_PARTS_NUMBER) { + throw new RuntimeException("Wrong parts in sketchy textproto parsing: " + lineParts); + } + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/BUILD new file mode 100644 index 000000000..85e5d90f6 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/BUILD @@ -0,0 +1,14 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "error", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_guava_guava", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/ErrorFileParser.java b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/ErrorFileParser.java new file mode 100644 index 000000000..492874951 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/ErrorFileParser.java @@ -0,0 +1,42 @@ +package org.jetbrains.bsp.bazel.server.bep.parsers.error; + +import org.jetbrains.bsp.bazel.commons.Constants; + +final class ErrorFileParser { + + private static final String AT_PATH = " at /"; + private static final String ERROR_PREAMBLE = "ERROR: "; + + public static String getFileWithError(String error) { + if (isInWorkspaceFile(error)) { + return Constants.WORKSPACE_FILE_NAME; + } + + return Constants.BUILD_FILE_NAME; + } + + public static boolean isInWorkspaceFile(String error) { + return error.contains("/" + Constants.WORKSPACE_FILE_NAME + ":"); + } + + public static boolean isInBuildFile(String error) { + return error.contains("/" + Constants.BUILD_FILE_NAME + ":"); + } + + public static String extractFileInfo(String error) { + int fileInfoStartIndex = getFileInfoStartIndex(error); + + return error.substring(fileInfoStartIndex); + } + + private static int getFileInfoStartIndex(String error) { + int atPathPrefixEndingIndex = error.indexOf(AT_PATH) + AT_PATH.length() - 1; + int errorPreamblePrefixEndingIndex = error.indexOf(ERROR_PREAMBLE) + ERROR_PREAMBLE.length(); + + if (error.contains(AT_PATH)) { + return atPathPrefixEndingIndex; + } + + return errorPreamblePrefixEndingIndex; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/ErrorPositionParser.java b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/ErrorPositionParser.java new file mode 100644 index 000000000..0c07154bc --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/ErrorPositionParser.java @@ -0,0 +1,52 @@ +package org.jetbrains.bsp.bazel.server.bep.parsers.error; + +import ch.epfl.scala.bsp4j.Position; + +final class ErrorPositionParser { + + private static final Integer ERROR_LINE_INDEX_OF_LINE = 0; + private static final Integer ERROR_LINE_INDEX_OF_CHARACTER = 1; + + private static final String AT_PATH = " at /"; + private static final String LINE_LOCATION_AT_PATH_DELIMITER = ":"; + private static final String LINE_LOCATION_DELIMITER = "(:)|( )"; + + public static Position getErrorPosition(String error) { + String[] lineLocation = getLineLocations(error); + + return getPosition(lineLocation); + } + + private static String[] getLineLocations(String error) { + String fileInfo = ErrorFileParser.extractFileInfo(error); + String erroredFile = ErrorFileParser.getFileWithError(error); + int locationUrlEndingIndex = fileInfo.indexOf(erroredFile) + erroredFile.length(); + + String lineLocationDelimiter = getLineLocationDelimiterRegardingErrorType(error); + + return fileInfo.substring(locationUrlEndingIndex + 1).split(lineLocationDelimiter); + } + + private static String getLineLocationDelimiterRegardingErrorType(String error) { + if (error.contains(AT_PATH)) { + return LINE_LOCATION_AT_PATH_DELIMITER; + } + + return LINE_LOCATION_DELIMITER; + } + + private static Position getPosition(String[] lineLocation) { + Integer lineNumber = getErrorLineNumber(lineLocation); + Integer characterIndex = getErrorCharacterIndex(lineLocation); + + return new Position(lineNumber, characterIndex); + } + + private static Integer getErrorLineNumber(String[] lineLocation) { + return Integer.parseInt(lineLocation[ERROR_LINE_INDEX_OF_LINE]); + } + + private static Integer getErrorCharacterIndex(String[] lineLocation) { + return Integer.parseInt(lineLocation[ERROR_LINE_INDEX_OF_CHARACTER]); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/FileDiagnostic.java b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/FileDiagnostic.java new file mode 100644 index 000000000..55c8924e1 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/FileDiagnostic.java @@ -0,0 +1,54 @@ +package org.jetbrains.bsp.bazel.server.bep.parsers.error; + +import ch.epfl.scala.bsp4j.Diagnostic; +import ch.epfl.scala.bsp4j.DiagnosticSeverity; +import ch.epfl.scala.bsp4j.Position; +import ch.epfl.scala.bsp4j.Range; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +class FileDiagnostic { + + private static final Logger LOGGER = LogManager.getLogger(FileDiagnostic.class); + + private final Diagnostic diagnostic; + private final String fileLocation; + + private FileDiagnostic(Diagnostic diagnostic, String fileLocation) { + this.diagnostic = diagnostic; + this.fileLocation = fileLocation; + } + + public static FileDiagnostic fromError(String error) { + LOGGER.info("Error: {}", error); + + String erroredFile = ErrorFileParser.getFileWithError(error); + String fileInfo = ErrorFileParser.extractFileInfo(error); + String fileLocation = getFileLocation(erroredFile, fileInfo); + + return createFileDiagnostic(error, fileLocation); + } + + private static FileDiagnostic createFileDiagnostic(String error, String fileLocation) { + Position position = ErrorPositionParser.getErrorPosition(error); + Range diagnosticRange = new Range(position, position); + Diagnostic diagnostic = new Diagnostic(diagnosticRange, error); + diagnostic.setSeverity(DiagnosticSeverity.ERROR); + + return new FileDiagnostic(diagnostic, fileLocation); + } + + private static String getFileLocation(String erroredFile, String fileInfo) { + int fileLocationUrlEndIndex = fileInfo.indexOf(erroredFile) + erroredFile.length(); + + return fileInfo.substring(0, fileLocationUrlEndIndex); + } + + public Diagnostic getDiagnostic() { + return diagnostic; + } + + public String getFileLocation() { + return fileLocation; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/StderrDiagnosticsParser.java b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/StderrDiagnosticsParser.java new file mode 100644 index 000000000..5292eeed9 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bep/parsers/error/StderrDiagnosticsParser.java @@ -0,0 +1,37 @@ +package org.jetbrains.bsp.bazel.server.bep.parsers.error; + +import ch.epfl.scala.bsp4j.Diagnostic; +import com.google.common.base.Splitter; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public final class StderrDiagnosticsParser { + + private static final String ERROR = "ERROR"; + + private static final String STDERR_DELIMITER = "\n"; + + public static Map> parse(String stderr) { + return splitStderr(stderr).stream() + .filter(StderrDiagnosticsParser::isError) + .filter(StderrDiagnosticsParser::isBazelError) + .map(FileDiagnostic::fromError) + .collect( + Collectors.groupingBy( + FileDiagnostic::getFileLocation, + Collectors.mapping(FileDiagnostic::getDiagnostic, Collectors.toList()))); + } + + private static List splitStderr(String stderr) { + return Splitter.on(STDERR_DELIMITER).splitToList(stderr); + } + + private static boolean isError(String stderrPart) { + return stderrPart.contains(ERROR); + } + + private static boolean isBazelError(String error) { + return ErrorFileParser.isInWorkspaceFile(error) || ErrorFileParser.isInBuildFile(error); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BUILD new file mode 100644 index 000000000..6c67d8747 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BUILD @@ -0,0 +1,28 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "bsp", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/data", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/params", + "//src/main/java/org/jetbrains/bsp/bazel/server/bep", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/config", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils", + "@io_bazel//src/main/protobuf:build_java_proto", + "@io_bazel//third_party/grpc:grpc-jar", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_code_gson_gson", + "@maven//:com_google_guava_guava", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", + "@maven//:org_eclipse_xtext_org_eclipse_xtext_xbase_lib", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BazelBspServerBuildManager.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BazelBspServerBuildManager.java new file mode 100644 index 000000000..383b2a25a --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BazelBspServerBuildManager.java @@ -0,0 +1,115 @@ +package org.jetbrains.bsp.bazel.server.bsp; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.CompileResult; +import ch.epfl.scala.bsp4j.SourceItem; +import ch.epfl.scala.bsp4j.WorkspaceBuildTargetsResult; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.commons.Lazy; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bep.BepServer; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspAspectsManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspCompilationManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspQueryManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspTargetManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelCppTargetManager; + +public class BazelBspServerBuildManager { + + public static final String BAZEL_PRINT_ASPECT = "@//.bazelbsp:aspects.bzl%print_aspect"; + + private final BazelBspServerRequestHelpers serverRequestHelpers; + private final BazelData bazelData; + private final BazelBspQueryManager bazelBspQueryManager; + private final BazelBspCompilationManager bazelBspCompilationManager; + private final BazelBspTargetManager bazelBspTargetManager; + private final BazelBspAspectsManager bazelBspAspectsManager; + private final BazelCppTargetManager bazelCppTargetManager; + + private BepServer bepServer; + + public BazelBspServerBuildManager( + BazelBspServerRequestHelpers serverRequestHelpers, + BazelData bazelData, + BazelRunner bazelRunner, + BazelBspCompilationManager bazelBspCompilationManager, + BazelBspAspectsManager bazelBspAspectsManager, + BazelBspTargetManager bazelBspTargetManager, + BazelCppTargetManager bazelCppTargetManager, + BazelBspQueryManager bazelBspQueryManager) { + this.serverRequestHelpers = serverRequestHelpers; + this.bazelData = bazelData; + this.bazelBspCompilationManager = bazelBspCompilationManager; + this.bazelBspAspectsManager = bazelBspAspectsManager; + this.bazelCppTargetManager = bazelCppTargetManager; + this.bazelBspTargetManager = bazelBspTargetManager; + this.bazelBspQueryManager = bazelBspQueryManager; + } + + public CompletableFuture getWorkspaceBuildTargets() { + return serverRequestHelpers.executeCommand( + "workspaceBuildTargets", bazelBspQueryManager::getWorkspaceBuildTargets); + } + + public List getSourceItems(Build.Rule rule, BuildTargetIdentifier label) { + return bazelBspQueryManager.getSourceItems(rule, label); + } + + public String getSourcesRoot(String uri) { + List root = + Constants.KNOWN_SOURCE_ROOTS.stream().filter(uri::contains).collect(Collectors.toList()); + return bazelData.getWorkspaceRoot() + + (root.size() == 0 + ? "" + : uri.substring(1, uri.indexOf(root.get(0)) + root.get(0).length())); + } + + public List lookUpTransitiveSourceJars(String target) { + // TODO(illicitonion): Use an aspect output group, rather than parsing stderr + // logging + return bazelBspAspectsManager + .fetchLinesFromAspect(target, BAZEL_PRINT_ASPECT) + .filter( + parts -> + parts.size() == 3 + && parts.get(0).equals(BazelBspAspectsManager.DEBUG_MESSAGE) + && parts.get(1).contains(BazelBspAspectsManager.ASPECT_LOCATION) + && parts.get(2).endsWith(".jar")) + .map(parts -> Constants.EXEC_ROOT_PREFIX + parts.get(2)) + .collect(Collectors.toList()); + } + + public List getResources(Build.Rule rule, Build.QueryResult queryResult) { + return bazelBspQueryManager.getResources(rule, queryResult); + } + + public void setBepServer(BepServer bepServer) { + this.bepServer = bepServer; + this.bazelBspQueryManager.setBepServer(bepServer); + this.bazelBspCompilationManager.setBepServer(bepServer); + this.bazelBspAspectsManager.setBepServer(bepServer); + } + + public Either buildTargetsWithBep( + List targets, ArrayList extraFlags) { + if (bepServer.getBuildTargetsSources().isEmpty()) { + bazelBspQueryManager.getWorkspaceBuildTargets(); + } + return bazelBspCompilationManager.buildTargetsWithBep(targets, extraFlags); + } + + public List> getLazyVals() { + return ImmutableList.of( + bazelBspTargetManager.getBazelBspJvmTargetManager(), + bazelBspTargetManager.getBazelBspScalaTargetManager()); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BazelBspServerLifetime.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BazelBspServerLifetime.java new file mode 100644 index 000000000..71980447e --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BazelBspServerLifetime.java @@ -0,0 +1,43 @@ +package org.jetbrains.bsp.bazel.server.bsp; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class BazelBspServerLifetime { + + private final CompletableFuture initializedStatus = new CompletableFuture<>(); + private final CompletableFuture finishedStatus = new CompletableFuture<>(); + + public boolean isInitialized() { + try { + initializedStatus.get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + return false; + } + return true; + } + + public boolean isFinished() { + return finishedStatus.isDone(); + } + + public void setInitializedComplete() { + initializedStatus.complete(null); + } + + public void setFinishedComplete() { + finishedStatus.complete(null); + } + + public void forceFinish() { + try { + finishedStatus.get(1, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + System.exit(1); + } + + System.exit(0); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BazelBspServerRequestHelpers.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BazelBspServerRequestHelpers.java new file mode 100644 index 000000000..687f85ea9 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BazelBspServerRequestHelpers.java @@ -0,0 +1,75 @@ +package org.jetbrains.bsp.bazel.server.bsp; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.lsp4j.jsonrpc.ResponseErrorException; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode; + +public class BazelBspServerRequestHelpers { + + private static final Logger LOGGER = LogManager.getLogger(BazelBspServerRequestHelpers.class); + + private final BazelBspServerLifetime serverLifetime; + + public BazelBspServerRequestHelpers(BazelBspServerLifetime serverLifetime) { + this.serverLifetime = serverLifetime; + } + + public CompletableFuture executeCommand( + String methodName, Supplier> request) { + if (!serverLifetime.isInitialized()) { + return completeExceptionally( + methodName, + new ResponseError( + ResponseErrorCode.serverNotInitialized, + "Server has not been initialized yet!", + false)); + } + if (serverLifetime.isFinished()) { + return completeExceptionally( + methodName, + new ResponseError( + ResponseErrorCode.serverErrorEnd, "Server has already shutdown!", false)); + } + + return getValue(methodName, request); + } + + public CompletableFuture getValue( + String methodName, Supplier> request) { + return CompletableFuture.supplyAsync(request) + .exceptionally( // TODO remove eithers in next PR + exception -> { + LOGGER.error( + "{} call finishing with exception: {}", + methodName, + exception.getCause().getStackTrace()); + + return Either.forLeft( + new ResponseError(ResponseErrorCode.InternalError, exception.getMessage(), null)); + }) + .thenComposeAsync( + either -> + either.isLeft() + ? completeExceptionally(methodName, either.getLeft()) + : completeWithSuccess(methodName, either.getRight())); + } + + public CompletableFuture completeExceptionally(String methodName, ResponseError error) { + LOGGER.error("{} call finishing with error: {}", methodName, error.getMessage()); + + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(new ResponseErrorException(error)); + return future; + } + + private CompletableFuture completeWithSuccess(String methodName, T response) { + LOGGER.info("{} call finishing with response: {}", methodName, response); + + return CompletableFuture.completedFuture(response); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BspImplementationHub.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BspImplementationHub.java new file mode 100644 index 000000000..e929c7adf --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BspImplementationHub.java @@ -0,0 +1,165 @@ +package org.jetbrains.bsp.bazel.server.bsp; + +import ch.epfl.scala.bsp4j.BuildServer; +import ch.epfl.scala.bsp4j.CleanCacheParams; +import ch.epfl.scala.bsp4j.CleanCacheResult; +import ch.epfl.scala.bsp4j.CompileParams; +import ch.epfl.scala.bsp4j.CompileResult; +import ch.epfl.scala.bsp4j.CppBuildServer; +import ch.epfl.scala.bsp4j.CppOptionsParams; +import ch.epfl.scala.bsp4j.CppOptionsResult; +import ch.epfl.scala.bsp4j.DependencyModulesParams; +import ch.epfl.scala.bsp4j.DependencyModulesResult; +import ch.epfl.scala.bsp4j.DependencySourcesParams; +import ch.epfl.scala.bsp4j.DependencySourcesResult; +import ch.epfl.scala.bsp4j.InitializeBuildParams; +import ch.epfl.scala.bsp4j.InitializeBuildResult; +import ch.epfl.scala.bsp4j.InverseSourcesParams; +import ch.epfl.scala.bsp4j.InverseSourcesResult; +import ch.epfl.scala.bsp4j.JavaBuildServer; +import ch.epfl.scala.bsp4j.JavacOptionsParams; +import ch.epfl.scala.bsp4j.JavacOptionsResult; +import ch.epfl.scala.bsp4j.ResourcesParams; +import ch.epfl.scala.bsp4j.ResourcesResult; +import ch.epfl.scala.bsp4j.RunParams; +import ch.epfl.scala.bsp4j.RunResult; +import ch.epfl.scala.bsp4j.ScalaBuildServer; +import ch.epfl.scala.bsp4j.ScalaMainClassesParams; +import ch.epfl.scala.bsp4j.ScalaMainClassesResult; +import ch.epfl.scala.bsp4j.ScalaTestClassesParams; +import ch.epfl.scala.bsp4j.ScalaTestClassesResult; +import ch.epfl.scala.bsp4j.ScalacOptionsParams; +import ch.epfl.scala.bsp4j.ScalacOptionsResult; +import ch.epfl.scala.bsp4j.SourcesParams; +import ch.epfl.scala.bsp4j.SourcesResult; +import ch.epfl.scala.bsp4j.TestParams; +import ch.epfl.scala.bsp4j.TestResult; +import ch.epfl.scala.bsp4j.WorkspaceBuildTargetsResult; +import java.util.concurrent.CompletableFuture; + +public class BspImplementationHub + implements BuildServer, ScalaBuildServer, JavaBuildServer, CppBuildServer { + + private final BuildServer buildServer; + private final ScalaBuildServer scalaBuildServer; + private final JavaBuildServer javaBuildServer; + private final CppBuildServer cppBuildServer; + + public BspImplementationHub( + BuildServer buildServer, + ScalaBuildServer scalaBuildServer, + JavaBuildServer javaBuildServer, + CppBuildServer cppBuildServer) { + this.buildServer = buildServer; + this.scalaBuildServer = scalaBuildServer; + this.javaBuildServer = javaBuildServer; + this.cppBuildServer = cppBuildServer; + } + + @Override + public CompletableFuture buildInitialize(InitializeBuildParams params) { + return buildServer.buildInitialize(params); + } + + @Override + public void onBuildInitialized() { + buildServer.onBuildInitialized(); + } + + @Override + public CompletableFuture buildShutdown() { + return buildServer.buildShutdown(); + } + + @Override + public void onBuildExit() { + buildServer.onBuildExit(); + } + + @Override + public CompletableFuture workspaceBuildTargets() { + return buildServer.workspaceBuildTargets(); + } + + @Override + public CompletableFuture workspaceReload() { + return buildServer.workspaceReload(); + } + + @Override + public CompletableFuture buildTargetSources(SourcesParams params) { + return buildServer.buildTargetSources(params); + } + + @Override + public CompletableFuture buildTargetInverseSources( + InverseSourcesParams params) { + return buildServer.buildTargetInverseSources(params); + } + + @Override + public CompletableFuture buildTargetDependencySources( + DependencySourcesParams params) { + return buildServer.buildTargetDependencySources(params); + } + + @Override + public CompletableFuture buildTargetResources(ResourcesParams params) { + return buildServer.buildTargetResources(params); + } + + @Override + public CompletableFuture buildTargetCompile(CompileParams params) { + return buildServer.buildTargetCompile(params); + } + + @Override + public CompletableFuture buildTargetTest(TestParams params) { + return buildServer.buildTargetTest(params); + } + + @Override + public CompletableFuture buildTargetRun(RunParams params) { + return buildServer.buildTargetRun(params); + } + + @Override + public CompletableFuture buildTargetCleanCache(CleanCacheParams params) { + return buildServer.buildTargetCleanCache(params); + } + + @Override + public CompletableFuture buildTargetDependencyModules( + DependencyModulesParams params) { + return buildServer.buildTargetDependencyModules(params); + } + + @Override + public CompletableFuture buildTargetScalacOptions( + ScalacOptionsParams params) { + return scalaBuildServer.buildTargetScalacOptions(params); + } + + @Override + public CompletableFuture buildTargetScalaTestClasses( + ScalaTestClassesParams params) { + return scalaBuildServer.buildTargetScalaTestClasses(params); + } + + @Override + public CompletableFuture buildTargetScalaMainClasses( + ScalaMainClassesParams params) { + return scalaBuildServer.buildTargetScalaMainClasses(params); + } + + @Override + public CompletableFuture buildTargetJavacOptions(JavacOptionsParams params) { + return javaBuildServer.buildTargetJavacOptions(params); + } + + @Override + public CompletableFuture buildTargetCppOptions( + CppOptionsParams cppOptionsParams) { + return cppBuildServer.buildTargetCppOptions(cppOptionsParams); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BspIntegrationData.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BspIntegrationData.java new file mode 100644 index 000000000..7c4373e76 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/BspIntegrationData.java @@ -0,0 +1,60 @@ +package org.jetbrains.bsp.bazel.server.bsp; + +import ch.epfl.scala.bsp4j.BuildClient; +import io.grpc.Server; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.concurrent.ExecutorService; +import org.eclipse.lsp4j.jsonrpc.Launcher; + +public class BspIntegrationData { + + private final PrintStream stdout; + private final InputStream stdin; + private final ExecutorService executor; + private final PrintWriter traceWriter; + + private Launcher launcher; + private Server server; + + public BspIntegrationData( + PrintStream stdout, InputStream stdin, ExecutorService executor, PrintWriter traceWriter) { + this.stdout = stdout; + this.stdin = stdin; + this.executor = executor; + this.traceWriter = traceWriter; + } + + public PrintStream getStdout() { + return stdout; + } + + public InputStream getStdin() { + return stdin; + } + + public ExecutorService getExecutor() { + return executor; + } + + public PrintWriter getTraceWriter() { + return traceWriter; + } + + public Launcher getLauncher() { + return launcher; + } + + public void setLauncher(Launcher launcher) { + this.launcher = launcher; + } + + public Server getServer() { + return server; + } + + public void setServer(Server server) { + this.server = server; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/config/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/config/BUILD new file mode 100644 index 000000000..f36eea81c --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/config/BUILD @@ -0,0 +1,12 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "config", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific", + "@maven//:com_google_guava_guava", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/config/BazelBspServerConfig.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/config/BazelBspServerConfig.java new file mode 100644 index 000000000..b4e8fed1f --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/config/BazelBspServerConfig.java @@ -0,0 +1,22 @@ +package org.jetbrains.bsp.bazel.server.bsp.config; + +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; + +public class BazelBspServerConfig { + + private final String pathToBazel; + private final ProjectView projectView; + + public BazelBspServerConfig(String pathToBazel, ProjectView projectView) { + this.pathToBazel = pathToBazel; + this.projectView = projectView; + } + + public String getBazelPath() { + return pathToBazel; + } + + public ProjectView getProjectView() { + return projectView; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/config/ServerArgsProjectViewProvider.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/config/ServerArgsProjectViewProvider.java new file mode 100644 index 000000000..5dff169b2 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/config/ServerArgsProjectViewProvider.java @@ -0,0 +1,28 @@ +package org.jetbrains.bsp.bazel.server.bsp.config; + +import com.google.common.collect.ImmutableList; +import java.nio.file.Paths; +import java.util.Arrays; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; +import org.jetbrains.bsp.bazel.projectview.model.ProjectViewProvider; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.DirectoriesSection; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.TargetsSection; + +public class ServerArgsProjectViewProvider implements ProjectViewProvider { + + private final String targets; + + public ServerArgsProjectViewProvider(String targets) { + this.targets = targets; + } + + @Override + public ProjectView create() { + DirectoriesSection directoriesSection = + new DirectoriesSection(ImmutableList.of(Paths.get(".")), ImmutableList.of()); + TargetsSection targetsSection = + new TargetsSection(Arrays.asList(targets.split(",")), ImmutableList.of()); + + return ProjectView.builder().directories(directoriesSection).targets(targetsSection).build(); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/BUILD new file mode 100644 index 000000000..c40e5908a --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/BUILD @@ -0,0 +1,14 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "impl", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/services", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_guava_guava", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/BuildServerImpl.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/BuildServerImpl.java new file mode 100644 index 000000000..9d1bab06c --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/BuildServerImpl.java @@ -0,0 +1,132 @@ +package org.jetbrains.bsp.bazel.server.bsp.impl; + +import ch.epfl.scala.bsp4j.BuildServer; +import ch.epfl.scala.bsp4j.CleanCacheParams; +import ch.epfl.scala.bsp4j.CleanCacheResult; +import ch.epfl.scala.bsp4j.CompileParams; +import ch.epfl.scala.bsp4j.CompileResult; +import ch.epfl.scala.bsp4j.DependencyModulesParams; +import ch.epfl.scala.bsp4j.DependencyModulesResult; +import ch.epfl.scala.bsp4j.DependencySourcesParams; +import ch.epfl.scala.bsp4j.DependencySourcesResult; +import ch.epfl.scala.bsp4j.InitializeBuildParams; +import ch.epfl.scala.bsp4j.InitializeBuildResult; +import ch.epfl.scala.bsp4j.InverseSourcesParams; +import ch.epfl.scala.bsp4j.InverseSourcesResult; +import ch.epfl.scala.bsp4j.ResourcesParams; +import ch.epfl.scala.bsp4j.ResourcesResult; +import ch.epfl.scala.bsp4j.RunParams; +import ch.epfl.scala.bsp4j.RunResult; +import ch.epfl.scala.bsp4j.SourcesParams; +import ch.epfl.scala.bsp4j.SourcesResult; +import ch.epfl.scala.bsp4j.TestParams; +import ch.epfl.scala.bsp4j.TestResult; +import ch.epfl.scala.bsp4j.WorkspaceBuildTargetsResult; +import com.google.common.collect.ImmutableList; +import java.util.concurrent.CompletableFuture; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerRequestHelpers; +import org.jetbrains.bsp.bazel.server.bsp.services.BuildServerService; + +public class BuildServerImpl implements BuildServer { + + private final BuildServerService buildServerService; + private final BazelBspServerRequestHelpers serverRequestHelpers; + + public BuildServerImpl( + BuildServerService buildServerService, BazelBspServerRequestHelpers serverRequestHelpers) { + this.buildServerService = buildServerService; + this.serverRequestHelpers = serverRequestHelpers; + } + + @Override + public CompletableFuture buildInitialize( + InitializeBuildParams initializeBuildParams) { + return buildServerService.buildInitialize(initializeBuildParams); + } + + @Override + public void onBuildInitialized() { + buildServerService.onBuildInitialized(); + } + + @Override + public CompletableFuture buildShutdown() { + return buildServerService.buildShutdown(); + } + + @Override + public void onBuildExit() { + buildServerService.onBuildExit(); + } + + @Override + public CompletableFuture workspaceBuildTargets() { + return buildServerService.workspaceBuildTargets(); + } + + @Override + public CompletableFuture workspaceReload() { + return serverRequestHelpers.executeCommand( + "workspaceReload", buildServerService::workspaceReload); + } + + @Override + public CompletableFuture buildTargetSources(SourcesParams sourcesParams) { + return serverRequestHelpers.executeCommand( + "buildTargetSources", () -> buildServerService.buildTargetSources(sourcesParams)); + } + + @Override + public CompletableFuture buildTargetInverseSources( + InverseSourcesParams inverseSourcesParams) { + return serverRequestHelpers.executeCommand( + "buildTargetInverseSources", + () -> buildServerService.buildTargetInverseSources(inverseSourcesParams)); + } + + @Override + public CompletableFuture buildTargetDependencySources( + DependencySourcesParams dependencySourcesParams) { + return serverRequestHelpers.executeCommand( + "buildTargetDependencySources", + () -> buildServerService.buildTargetDependencySources(dependencySourcesParams)); + } + + @Override + public CompletableFuture buildTargetResources(ResourcesParams resourcesParams) { + return serverRequestHelpers.executeCommand( + "buildTargetResources", () -> buildServerService.buildTargetResources(resourcesParams)); + } + + @Override + public CompletableFuture buildTargetCompile(CompileParams compileParams) { + return serverRequestHelpers.executeCommand( + "buildTargetCompile", () -> buildServerService.buildTargetCompile(compileParams)); + } + + @Override + public CompletableFuture buildTargetTest(TestParams testParams) { + return serverRequestHelpers.executeCommand( + "buildTargetTest", () -> buildServerService.buildTargetTest(testParams)); + } + + @Override + public CompletableFuture buildTargetRun(RunParams runParams) { + return serverRequestHelpers.executeCommand( + "buildTargetRun", () -> buildServerService.buildTargetRun(runParams)); + } + + @Override + public CompletableFuture buildTargetCleanCache( + CleanCacheParams cleanCacheParams) { + return serverRequestHelpers.executeCommand( + "buildTargetCleanCache", () -> buildServerService.buildTargetCleanCache(cleanCacheParams)); + } + + // TODO: Implement Dependency Modules + @Override + public CompletableFuture buildTargetDependencyModules( + DependencyModulesParams params) { + return CompletableFuture.completedFuture(new DependencyModulesResult(ImmutableList.of())); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/CppBuildServerImpl.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/CppBuildServerImpl.java new file mode 100644 index 000000000..db6c082e0 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/CppBuildServerImpl.java @@ -0,0 +1,28 @@ +package org.jetbrains.bsp.bazel.server.bsp.impl; + +import ch.epfl.scala.bsp4j.CppBuildServer; +import ch.epfl.scala.bsp4j.CppOptionsParams; +import ch.epfl.scala.bsp4j.CppOptionsResult; +import java.util.concurrent.CompletableFuture; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerRequestHelpers; +import org.jetbrains.bsp.bazel.server.bsp.services.CppBuildServerService; + +public class CppBuildServerImpl implements CppBuildServer { + private final CppBuildServerService cppBuildServerService; + private final BazelBspServerRequestHelpers serverRequestHelpers; + + public CppBuildServerImpl( + CppBuildServerService cppBuildServerService, + BazelBspServerRequestHelpers serverRequestHelpers) { + this.cppBuildServerService = cppBuildServerService; + this.serverRequestHelpers = serverRequestHelpers; + } + + @Override + public CompletableFuture buildTargetCppOptions( + CppOptionsParams cppOptionsParams) { + return serverRequestHelpers.executeCommand( + "buildTargetCppOptions", + () -> cppBuildServerService.buildTargetCppOptions(cppOptionsParams)); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/JavaBuildServerImpl.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/JavaBuildServerImpl.java new file mode 100644 index 000000000..cbec590ba --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/JavaBuildServerImpl.java @@ -0,0 +1,29 @@ +package org.jetbrains.bsp.bazel.server.bsp.impl; + +import ch.epfl.scala.bsp4j.JavaBuildServer; +import ch.epfl.scala.bsp4j.JavacOptionsParams; +import ch.epfl.scala.bsp4j.JavacOptionsResult; +import java.util.concurrent.CompletableFuture; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerRequestHelpers; +import org.jetbrains.bsp.bazel.server.bsp.services.JavaBuildServerService; + +public class JavaBuildServerImpl implements JavaBuildServer { + + private final JavaBuildServerService javaBuildServerService; + private final BazelBspServerRequestHelpers serverRequestHelpers; + + public JavaBuildServerImpl( + JavaBuildServerService javaBuildServerService, + BazelBspServerRequestHelpers serverRequestHelpers) { + this.javaBuildServerService = javaBuildServerService; + this.serverRequestHelpers = serverRequestHelpers; + } + + @Override + public CompletableFuture buildTargetJavacOptions( + JavacOptionsParams javacOptionsParams) { + return serverRequestHelpers.executeCommand( + "buildTargetJavacOptions", + () -> javaBuildServerService.buildTargetJavacOptions(javacOptionsParams)); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/ScalaBuildServerImpl.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/ScalaBuildServerImpl.java new file mode 100644 index 000000000..ab24e0f28 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/impl/ScalaBuildServerImpl.java @@ -0,0 +1,49 @@ +package org.jetbrains.bsp.bazel.server.bsp.impl; + +import ch.epfl.scala.bsp4j.ScalaBuildServer; +import ch.epfl.scala.bsp4j.ScalaMainClassesParams; +import ch.epfl.scala.bsp4j.ScalaMainClassesResult; +import ch.epfl.scala.bsp4j.ScalaTestClassesParams; +import ch.epfl.scala.bsp4j.ScalaTestClassesResult; +import ch.epfl.scala.bsp4j.ScalacOptionsParams; +import ch.epfl.scala.bsp4j.ScalacOptionsResult; +import java.util.concurrent.CompletableFuture; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerRequestHelpers; +import org.jetbrains.bsp.bazel.server.bsp.services.ScalaBuildServerService; + +public class ScalaBuildServerImpl implements ScalaBuildServer { + + private final ScalaBuildServerService scalaBuildServerService; + private final BazelBspServerRequestHelpers serverRequestHelpers; + + public ScalaBuildServerImpl( + ScalaBuildServerService scalaBuildServerService, + BazelBspServerRequestHelpers serverRequestHelpers) { + this.scalaBuildServerService = scalaBuildServerService; + this.serverRequestHelpers = serverRequestHelpers; + } + + @Override + public CompletableFuture buildTargetScalacOptions( + ScalacOptionsParams scalacOptionsParams) { + return serverRequestHelpers.executeCommand( + "buildTargetScalacOptions", + () -> scalaBuildServerService.buildTargetScalacOptions(scalacOptionsParams)); + } + + @Override + public CompletableFuture buildTargetScalaTestClasses( + ScalaTestClassesParams scalaTestClassesParams) { + return serverRequestHelpers.executeCommand( + "buildTargetScalaTestClasses", + () -> scalaBuildServerService.buildTargetScalaTestClasses(scalaTestClassesParams)); + } + + @Override + public CompletableFuture buildTargetScalaMainClasses( + ScalaMainClassesParams scalaMainClassesParams) { + return serverRequestHelpers.executeCommand( + "buildTargetScalaMainClasses", + () -> scalaBuildServerService.buildTargetScalaMainClasses(scalaMainClassesParams)); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD new file mode 100644 index 000000000..a6bdb11a5 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BUILD @@ -0,0 +1,27 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "managers", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/data", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/params", + "//src/main/java/org/jetbrains/bsp/bazel/server/bep", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/config", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils", + "@io_bazel//src/main/protobuf:build_java_proto", + "@io_bazel//third_party/grpc:grpc-jar", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_code_gson_gson", + "@maven//:com_google_guava_guava", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", + "@maven//:org_eclipse_xtext_org_eclipse_xtext_xbase_lib", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspAspectsManager.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspAspectsManager.java new file mode 100644 index 000000000..91808a197 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspAspectsManager.java @@ -0,0 +1,61 @@ +package org.jetbrains.bsp.bazel.server.bsp.managers; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.commons.Uri; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; +import org.jetbrains.bsp.bazel.server.bep.BepServer; + +public class BazelBspAspectsManager { + + public static final String DEBUG_MESSAGE = "DEBUG:"; + public static final String ASPECT_LOCATION = ".bazelbsp/aspects.bzl"; + private final BazelBspCompilationManager bazelBspCompilationManager; + private final BazelRunner bazelRunner; + private BepServer bepServer; + + public BazelBspAspectsManager( + BazelBspCompilationManager bazelBspCompilationManager, BazelRunner bazelRunner) { + this.bazelBspCompilationManager = bazelBspCompilationManager; + this.bazelRunner = bazelRunner; + } + + public List fetchPathsFromOutputGroup( + List targets, String aspect, String outputGroup) { + String aspectFlag = String.format("--aspects=%s", aspect); + String outputGroupFlag = String.format("--output_groups=%s", outputGroup); + bazelBspCompilationManager.buildTargetsWithBep( + targets, ImmutableList.of(aspectFlag, outputGroupFlag)); + return bepServer + .getOutputGroupPaths() + .getOrDefault(Constants.SCALA_COMPILER_CLASSPATH_FILES, Collections.emptySet()) + .stream() + .map(Uri::toString) + .collect(Collectors.toList()); + } + + public Stream> fetchLinesFromAspect(String target, String aspect) { + List lines = + bazelRunner + .commandBuilder() + .build() + .withFlag(BazelRunnerFlag.NOBUILD) + .withFlag(BazelRunnerFlag.ASPECTS, aspect) + .withArgument(target) + .executeBazelCommand() + .getStderr(); + + return lines.stream().map(line -> Splitter.on(" ").splitToList(line)); + } + + public void setBepServer(BepServer bepServer) { + this.bepServer = bepServer; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspCompilationManager.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspCompilationManager.java new file mode 100644 index 000000000..f180da88c --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspCompilationManager.java @@ -0,0 +1,106 @@ +package org.jetbrains.bsp.bazel.server.bsp.managers; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.CompileResult; +import ch.epfl.scala.bsp4j.StatusCode; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.server.bazel.BazelProcess; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; +import org.jetbrains.bsp.bazel.server.bep.BepServer; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.QueryResolver; +import org.jetbrains.bsp.bazel.server.bsp.utils.BuildManagerParsingUtils; + +public class BazelBspCompilationManager { + private static final Logger LOGGER = LogManager.getLogger(BazelBspCompilationManager.class); + + private BepServer bepServer; + private final BazelRunner bazelRunner; + private final BazelData bazelData; + + public BazelBspCompilationManager(BazelRunner bazelRunner, BazelData bazelData) { + this.bazelRunner = bazelRunner; + this.bazelData = bazelData; + } + + public Either buildTargetsWithBep( + List targets, List extraFlags) { + List bazelTargets = + targets.stream().map(BuildTargetIdentifier::getUri).collect(Collectors.toList()); + + final Map diagnosticsProtosLocations = + bepServer.getDiagnosticsProtosLocations(); + BazelProcess bazelProcess = + bazelRunner + .commandBuilder() + .query() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withTargets(bazelTargets) + .executeBazelBesCommand(); + + Build.QueryResult queryResult = QueryResolver.getQueryResultForProcess(bazelProcess); + + cacheProtoLocations(diagnosticsProtosLocations, queryResult); + + StatusCode exitCode = + bazelRunner + .commandBuilder() + .build() + .withFlags(extraFlags) + .withTargets(bazelTargets) + .executeBazelBesCommand() + .waitAndGetResult() + .getStatusCode(); + + emitDiagnosticsFromCache(diagnosticsProtosLocations); + + return Either.forRight(new CompileResult(exitCode)); + } + + private void emitDiagnosticsFromCache(Map diagnosticsProtosLocations) { + for (Map.Entry diagnostics : diagnosticsProtosLocations.entrySet()) { + String target = diagnostics.getKey(); + String diagnosticsPath = diagnostics.getValue(); + BuildTargetIdentifier targetIdentifier = new BuildTargetIdentifier(target); + // TODO (abrams27) is it ok? + bepServer.emitDiagnostics( + bepServer.collectDiagnostics(targetIdentifier, diagnosticsPath), targetIdentifier); + } + } + + private void cacheProtoLocations( + Map diagnosticsProtosLocations, Build.QueryResult queryResult) { + queryResult.getTargetList().stream() + .map(Build.Target::getRule) + .filter(this::isWorkspacePackage) + .forEach( + rule -> + rule.getRuleOutputList().stream() + .filter(output -> output.contains(Constants.DIAGNOSTICS)) + .forEach(output -> cacheProtos(diagnosticsProtosLocations, rule, output))); + } + + private void cacheProtos( + Map diagnosticsProtosLocations, Build.Rule rule, String output) { + diagnosticsProtosLocations.put( + rule.getName(), + BuildManagerParsingUtils.convertOutputToPath(output, bazelData.getBinRoot())); + } + + private boolean isWorkspacePackage(Build.Rule rule) { + return !rule.getName().startsWith("@"); + } + + public void setBepServer(BepServer bepServer) { + this.bepServer = bepServer; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspJvmTargetManager.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspJvmTargetManager.java new file mode 100644 index 000000000..b92a96120 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspJvmTargetManager.java @@ -0,0 +1,99 @@ +package org.jetbrains.bsp.bazel.server.bsp.managers; + +import ch.epfl.scala.bsp4j.JvmBuildTarget; +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; +import org.jetbrains.bsp.bazel.commons.Lazy; +import org.jetbrains.bsp.bazel.commons.Uri; +import org.jetbrains.bsp.bazel.server.bazel.BazelProcess; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.QueryResolver; + +public class BazelBspJvmTargetManager extends Lazy { + public static final String FETCH_JAVA_VERSION_ASPECT = + "@//.bazelbsp:aspects.bzl%fetch_java_target_version"; + public static final String BAZEL_JDK_CURRENT_JAVA_TOOLCHAIN = + "@bazel_tools//tools/jdk:current_java_toolchain"; + private final BazelRunner bazelRunner; + private final BazelBspAspectsManager bazelBspAspectsManager; + + public BazelBspJvmTargetManager( + BazelRunner bazelRunner, BazelBspAspectsManager bazelBspAspectsManager) { + this.bazelRunner = bazelRunner; + this.bazelBspAspectsManager = bazelBspAspectsManager; + } + + public JvmBuildTarget getJVMBuildTarget(Build.Rule rule) { + Optional javaHomePath = getJavaPath(rule); + Optional javaVersion = getJavaVersion(); + return new JvmBuildTarget(javaHomePath.orElse(null), javaVersion.orElse(null)); + } + + private Optional getJavaPath(Build.Rule rule) { + List traversingPath = Lists.newArrayList("$jvm", "$java_runtime", ":alias", "actual"); + + return traverseDependency(rule, traversingPath) + .map(Build.Rule::getLocation) + .map(location -> location.substring(0, location.indexOf("/BUILD"))) + .map(path -> Uri.fromAbsolutePath(path).toString()); + } + + private Optional traverseDependency( + Build.Rule startingRule, List attributesToTraverse) { + Build.Rule currentRule = startingRule; + + for (String attributeToTraverse : attributesToTraverse) { + Optional rule = + currentRule.getAttributeList().stream() + .filter( + attribute -> + attribute.getName().equals(attributeToTraverse) && attribute.hasStringValue()) + .findFirst() + .flatMap(this::getTargetFromAttribute) + .map(Build.Target::getRule); + + if (!rule.isPresent()) { + return Optional.empty(); + } + + currentRule = rule.get(); + } + + return Optional.of(currentRule); + } + + private Optional getTargetFromAttribute(Build.Attribute attribute) { + BazelProcess processResult = + bazelRunner + .commandBuilder() + .query() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withArgument(attribute.getStringValue()) + .executeBazelBesCommand(); + + return QueryResolver.getQueryResultForProcess(processResult).getTargetList().stream() + .findFirst(); + } + + private Optional getJavaVersion() { + return bazelBspAspectsManager + .fetchLinesFromAspect(BAZEL_JDK_CURRENT_JAVA_TOOLCHAIN, FETCH_JAVA_VERSION_ASPECT) + .filter( + parts -> + parts.size() == 3 + && parts.get(0).equals(BazelBspAspectsManager.DEBUG_MESSAGE) + && parts.get(1).contains(BazelBspAspectsManager.ASPECT_LOCATION) + && parts.get(2).chars().allMatch(Character::isDigit)) + .map(parts -> parts.get(2)) + .findFirst(); + } + + @Override + protected Supplier> calculateValue() { + return this::getJavaVersion; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspQueryManager.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspQueryManager.java new file mode 100644 index 000000000..c2e027899 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspQueryManager.java @@ -0,0 +1,268 @@ +package org.jetbrains.bsp.bazel.server.bsp.managers; + +import ch.epfl.scala.bsp4j.BuildTarget; +import ch.epfl.scala.bsp4j.BuildTargetCapabilities; +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.SourceItem; +import ch.epfl.scala.bsp4j.SourceItemKind; +import ch.epfl.scala.bsp4j.WorkspaceBuildTargetsResult; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.commons.Uri; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; +import org.jetbrains.bsp.bazel.server.bazel.BazelProcess; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelQueryKindParameters; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; +import org.jetbrains.bsp.bazel.server.bep.BepServer; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.QueryResolver; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.TargetsUtils; + +public class BazelBspQueryManager { + private static final Logger LOGGER = LogManager.getLogger(BazelBspQueryManager.class); + + private final ProjectView projectView; + private final BazelData bazelData; + private final BazelRunner bazelRunner; + private final BazelBspTargetManager bazelBspTargetManager; + private BepServer bepServer; + + public BazelBspQueryManager( + ProjectView projectView, + BazelData bazelData, + BazelRunner bazelRunner, + BazelBspTargetManager bazelBspTargetManager) { + this.projectView = projectView; + this.bazelData = bazelData; + this.bazelRunner = bazelRunner; + this.bazelBspTargetManager = bazelBspTargetManager; + } + + public Either getWorkspaceBuildTargets() { + List projectInitTargets = projectView.getTargets().getIncludedTargets(); + + List targets = + projectInitTargets.stream() + .map(this::getBuildTargetForProjectPath) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + + return Either.forRight(new WorkspaceBuildTargetsResult(targets)); + } + + public List getSourceItems(Build.Rule rule, BuildTargetIdentifier label) { + List srcs = getSrcs(rule, false); + srcs.addAll(getSrcs(rule, true)); + // TODO (abrams27) fix updating + bepServer.getBuildTargetsSources().put(label, srcs); + return srcs; + } + + private BuildTarget getBuildTargetForRule(Build.Rule rule) { + String name = rule.getName(); + LOGGER.info("Getting targets for rule: " + name); + + List deps = + rule.getAttributeList().stream() + .filter(attribute -> attribute.getName().equals("deps")) + .flatMap(srcDeps -> srcDeps.getStringListValueList().stream()) + .map(BuildTargetIdentifier::new) + .collect(Collectors.toList()); + BuildTargetIdentifier label = new BuildTargetIdentifier(name); + + List sources = getSourceItems(rule, label); + Set extensions = new TreeSet<>(); + + for (SourceItem source : sources) { + if (Constants.SCALA_EXTENSIONS.stream().anyMatch(ext -> source.getUri().endsWith(ext))) { + extensions.add(Constants.SCALA); + } else if (Constants.JAVA_EXTENSIONS.stream() + .anyMatch(ext -> source.getUri().endsWith(ext))) { + extensions.add(Constants.JAVA); + } else if (Constants.KOTLIN_EXTENSIONS.stream() + .anyMatch(ext -> source.getUri().endsWith(ext))) { + extensions.add(Constants.KOTLIN); + extensions.add( + Constants.JAVA); // TODO(andrefmrocha): Remove this when kotlin is natively supported + } else if (Constants.CPP_EXTENSIONS.stream().anyMatch(ext -> source.getUri().endsWith(ext))) { + extensions.add(Constants.CPP); + } + } + + String ruleClass = rule.getRuleClass(); + BuildTarget target = + new BuildTarget( + label, + new ArrayList<>(), + new ArrayList<>(extensions), + deps, + new BuildTargetCapabilities( + true, + ruleClass.endsWith("_" + Constants.TEST_RULE_TYPE), + ruleClass.endsWith("_" + Constants.BINARY_RULE_TYPE))); + target.setBaseDirectory( + Uri.packageDirFromLabel(label.getUri(), bazelData.getWorkspaceRoot()).toString()); + target.setDisplayName(label.getUri()); + bazelBspTargetManager.fillTargetData(target, extensions, ruleClass, rule); + return target; + } + + private List getBuildTargetForProjectPath(String target) { + String targetWithExcludedTargets = + TargetsUtils.getTargetWithExcludedTargets(projectView, target); + List kindParameters = + ImmutableList.of( + BazelQueryKindParameters.fromPatternAndInput("binary", targetWithExcludedTargets), + BazelQueryKindParameters.fromPatternAndInput("library", targetWithExcludedTargets), + BazelQueryKindParameters.fromPatternAndInput("test", targetWithExcludedTargets)); + + BazelProcess bazelProcess = + bazelRunner + .commandBuilder() + .query() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withFlag(BazelRunnerFlag.NOHOST_DEPS) + .withFlag(BazelRunnerFlag.NOIMPLICIT_DEPS) + .withKinds(kindParameters) + .executeBazelBesCommand(); + + Build.QueryResult queryResult = QueryResolver.getQueryResultForProcess(bazelProcess); + + return queryResult.getTargetList().stream() + .map(Build.Target::getRule) + .filter(rule -> !rule.getRuleClass().equals("filegroup")) + .map(this::getBuildTargetForRule) + .collect(Collectors.toList()); + } + + public List getTargetIdentifiersForDependencies( + List targets) { + String targetsUnion = TargetsUtils.getTargetsUnion(targets); + BazelQueryKindParameters kindParameters = + BazelQueryKindParameters.fromPatternAndInput( + "rule", String.format("deps(%s)", targetsUnion)); + + BazelProcess bazelProcess = + bazelRunner + .commandBuilder() + .query() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withFlag(BazelRunnerFlag.NOHOST_DEPS) + .withFlag(BazelRunnerFlag.KEEP_GOING) + .withKindsAndExcept(kindParameters, "//...") + .executeBazelBesCommand(); + + Build.QueryResult queryResult = QueryResolver.getQueryResultForProcess(bazelProcess); + + return queryResult.getTargetList().stream() + .map(Build.Target::getRule) + .map(Build.Rule::getName) + .map(BuildTargetIdentifier::new) + .collect(Collectors.toList()); + } + + public List getResources(Build.Rule rule, Build.QueryResult queryResult) { + return rule.getAttributeList().stream() + .filter( + attribute -> + attribute.getName().equals("resources") + && attribute.hasExplicitlySpecified() + && attribute.getExplicitlySpecified()) + .flatMap( + attribute -> { + List targetsRule = + attribute.getStringListValueList().stream() + .map(label -> isPackage(queryResult, label)) + .filter(targets -> !targets.isEmpty()) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + List targetsResources = getResourcesOutOfRule(targetsRule); + + List resources = + attribute.getStringListValueList().stream() + .filter(label -> isPackage(queryResult, label).isEmpty()) + .map( + label -> + Uri.fromFileLabel(label, bazelData.getWorkspaceRoot()).toString()) + .collect(Collectors.toList()); + + return Stream.concat(targetsResources.stream(), resources.stream()); + }) + .collect(Collectors.toList()); + } + + private List isPackage(Build.QueryResult queryResult, String label) { + return queryResult.getTargetList().stream() + .filter(target -> target.hasRule() && target.getRule().getName().equals(label)) + .collect(Collectors.toList()); + } + + private List getResourcesOutOfRule(List rules) { + return rules.stream() + .flatMap(resourceRule -> resourceRule.getRule().getAttributeList().stream()) + .filter((srcAttribute) -> srcAttribute.getName().equals("srcs")) + .flatMap(resourceAttribute -> resourceAttribute.getStringListValueList().stream()) + .map(src -> Uri.fromFileLabel(src, bazelData.getWorkspaceRoot()).toString()) + .collect(Collectors.toList()); + } + + private List getSrcs(Build.Rule rule, boolean isGenerated) { + String srcType = isGenerated ? "generated_srcs" : "srcs"; + return getSrcsPaths(rule, srcType).stream() + .map(uri -> new SourceItem(uri.toString(), SourceItemKind.FILE, isGenerated)) + .collect(Collectors.toList()); + } + + private List getSrcsPaths(Build.Rule rule, String srcType) { + return rule.getAttributeList().stream() + .filter(attribute -> attribute.getName().equals(srcType)) + .flatMap(srcsSrc -> srcsSrc.getStringListValueList().stream()) + .flatMap( + dep -> { + if (isSourceFile(dep)) { + return Lists.newArrayList(Uri.fromFileLabel(dep, bazelData.getWorkspaceRoot())) + .stream(); + } + BazelProcess bazelProcess = + bazelRunner + .commandBuilder() + .query() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withArgument(dep) + .executeBazelBesCommand(); + + Build.QueryResult queryResult = QueryResolver.getQueryResultForProcess(bazelProcess); + + return queryResult.getTargetList().stream() + .map(Build.Target::getRule) + .flatMap(queryRule -> getSrcsPaths(queryRule, srcType).stream()) + .collect(Collectors.toList()) + .stream(); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private boolean isSourceFile(String dep) { + return Constants.FILE_EXTENSIONS.stream().anyMatch(dep::endsWith) && !dep.startsWith("@"); + } + + public void setBepServer(BepServer bepServer) { + this.bepServer = bepServer; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspScalaTargetManager.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspScalaTargetManager.java new file mode 100644 index 000000000..bea1ea489 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspScalaTargetManager.java @@ -0,0 +1,74 @@ +package org.jetbrains.bsp.bazel.server.bsp.managers; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.ScalaBuildTarget; +import ch.epfl.scala.bsp4j.ScalaPlatform; +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.bsp.bazel.commons.Lazy; + +public class BazelBspScalaTargetManager extends Lazy { + private static final Logger LOGGER = LogManager.getLogger(BazelBspScalaTargetManager.class); + + public static final String SCALA_LIBRARY = + "@io_bazel_rules_scala_scala_library//:io_bazel_rules_scala_scala_library"; + public static final String SCALA_REFLECT = + "@io_bazel_rules_scala_scala_reflect//:io_bazel_rules_scala_scala_reflect"; + public static final String SCALA_COMPILER = + "@io_bazel_rules_scala_scala_compiler//:io_bazel_rules_scala_scala_compiler"; + public static final String SCALA_COMPILER_ASPECT = + "@//.bazelbsp:aspects.bzl%scala_compiler_classpath_aspect"; + public static final String SCALA_COMPILER_OUTPUT_GROUP = "scala_compiler_classpath_files"; + private final BazelBspAspectsManager bazelBspAspectsManager; + + public BazelBspScalaTargetManager(BazelBspAspectsManager bazelBspAspectsManager) { + this.bazelBspAspectsManager = bazelBspAspectsManager; + } + + protected Optional getScalaBuildTarget() { + List targets = + ImmutableList.of( + new BuildTargetIdentifier(SCALA_LIBRARY), + new BuildTargetIdentifier(SCALA_REFLECT), + new BuildTargetIdentifier(SCALA_COMPILER)); + List classpath = + bazelBspAspectsManager.fetchPathsFromOutputGroup( + targets, SCALA_COMPILER_ASPECT, SCALA_COMPILER_OUTPUT_GROUP); + + List scalaVersions = + classpath.stream() + .filter(uri -> uri.contains("scala-library")) + .collect(Collectors.toList()); + + if (scalaVersions.size() != 1) { + LOGGER.error("Scala versions size different than one: " + scalaVersions.size()); + return Optional.empty(); + } + + String scalaVersion = + scalaVersions + .get(0) + .substring( + scalaVersions.get(0).indexOf("scala-library-") + 14, + scalaVersions.get(0).indexOf(".jar")); + ScalaBuildTarget scalaBuildTarget = + new ScalaBuildTarget( + "org.scala-lang", + scalaVersion, + scalaVersion.substring(0, scalaVersion.lastIndexOf(".")), + ScalaPlatform.JVM, + classpath); + + return Optional.of(scalaBuildTarget); + } + + @Override + protected Supplier> calculateValue() { + return this::getScalaBuildTarget; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspTargetManager.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspTargetManager.java new file mode 100644 index 000000000..26c55986b --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelBspTargetManager.java @@ -0,0 +1,72 @@ +package org.jetbrains.bsp.bazel.server.bsp.managers; + +import ch.epfl.scala.bsp4j.BuildTarget; +import ch.epfl.scala.bsp4j.BuildTargetDataKind; +import ch.epfl.scala.bsp4j.ScalaBuildTarget; +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import java.util.Optional; +import java.util.Set; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bsp.utils.BuildManagerParsingUtils; + +public class BazelBspTargetManager { + private final BazelBspScalaTargetManager bazelBspScalaTargetManager; + private final BazelBspJvmTargetManager bazelBspJvmTargetManager; + private final BazelCppTargetManager bazelCppTargetManager; + + public BazelBspTargetManager( + BazelRunner bazelRunner, + BazelBspAspectsManager bazelBspAspectsManager, + BazelCppTargetManager bazelCppTargetManager) { + this.bazelBspScalaTargetManager = new BazelBspScalaTargetManager(bazelBspAspectsManager); + this.bazelCppTargetManager = bazelCppTargetManager; + this.bazelBspJvmTargetManager = + new BazelBspJvmTargetManager(bazelRunner, bazelBspAspectsManager); + } + + private Optional getScalaBuildTarget(Build.Rule rule) { + return bazelBspScalaTargetManager + .getValue() + .map( + target -> { + target.setJvmBuildTarget(bazelBspJvmTargetManager.getJVMBuildTarget(rule)); + return target; + }); + } + + public void fillTargetData( + BuildTarget target, Set extensions, String ruleClass, Build.Rule rule) { + if (extensions.contains(Constants.SCALA)) { + getScalaBuildTarget(rule) + .ifPresent( + (buildTarget) -> { + target.setDataKind(BuildTargetDataKind.SCALA); + target.setTags(Lists.newArrayList(BuildManagerParsingUtils.getRuleType(ruleClass))); + target.setData(buildTarget); + }); + } else if (extensions.contains(Constants.JAVA) || extensions.contains(Constants.KOTLIN)) { + target.setDataKind(BuildTargetDataKind.JVM); + target.setTags(Lists.newArrayList(BuildManagerParsingUtils.getRuleType(ruleClass))); + target.setData(bazelBspJvmTargetManager.getJVMBuildTarget(rule)); + } else if (extensions.contains(Constants.CPP)) { + bazelCppTargetManager + .getValue() + .ifPresent( + buildTarget -> { + target.setDataKind(BuildTargetDataKind.CPP); + target.setTags(Lists.newArrayList(BuildManagerParsingUtils.getRuleType(ruleClass))); + target.setData(buildTarget); + }); + } + } + + public BazelBspScalaTargetManager getBazelBspScalaTargetManager() { + return bazelBspScalaTargetManager; + } + + public BazelBspJvmTargetManager getBazelBspJvmTargetManager() { + return bazelBspJvmTargetManager; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelCppTargetManager.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelCppTargetManager.java new file mode 100644 index 000000000..7fe4b134f --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers/BazelCppTargetManager.java @@ -0,0 +1,43 @@ +package org.jetbrains.bsp.bazel.server.bsp.managers; + +import ch.epfl.scala.bsp4j.CppBuildTarget; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.jetbrains.bsp.bazel.commons.Lazy; + +public class BazelCppTargetManager extends Lazy { + private static final String BAZEL_CPP_TOOLCHAIN = "@bazel_tools//tools/cpp:toolchain"; + private static final String FETCH_CPP_ASPECT = "@//.bazelbsp:aspects.bzl%fetch_cpp_compiler"; + private final BazelBspAspectsManager bazelBspAspectsManager; + + public BazelCppTargetManager(BazelBspAspectsManager bazelBspAspectsManager) { + this.bazelBspAspectsManager = bazelBspAspectsManager; + } + + private Optional getCppBuildTarget() { + List cppInfo = + bazelBspAspectsManager + .fetchLinesFromAspect(BAZEL_CPP_TOOLCHAIN, FETCH_CPP_ASPECT) + .filter( + parts -> + parts.size() == 3 + && parts.get(0).equals(BazelBspAspectsManager.DEBUG_MESSAGE) + && parts.get(1).contains(BazelBspAspectsManager.ASPECT_LOCATION)) + .map(parts -> parts.get(2)) + .limit(2) + .collect(Collectors.toList()); + + if (cppInfo.isEmpty()) return Optional.empty(); + + String compiler = cppInfo.get(0); + String compilerExecutable = cppInfo.get(1); + return Optional.of(new CppBuildTarget(null, compiler, compilerExecutable, compilerExecutable)); + } + + @Override + protected Supplier> calculateValue() { + return this::getCppBuildTarget; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/ActionGraphResolver.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/ActionGraphResolver.java new file mode 100644 index 000000000..eeb46cc3f --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/ActionGraphResolver.java @@ -0,0 +1,50 @@ +package org.jetbrains.bsp.bazel.server.bsp.resolvers; + +import com.google.devtools.build.lib.analysis.AnalysisProtos; +import com.google.devtools.build.lib.analysis.AnalysisProtosV2; +import java.io.IOException; +import java.util.List; +import org.jetbrains.bsp.bazel.server.bazel.BazelProcess; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bazel.data.SemanticVersion; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.actiongraph.ActionGraphParser; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.actiongraph.ActionGraphV1Parser; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.actiongraph.ActionGraphV2Parser; + +public class ActionGraphResolver { + + private final BazelRunner bazelRunner; + private final BazelData bazelData; + private static final SemanticVersion ACTION_GRAPH_V2_VERSION = new SemanticVersion("4.0.0"); + + public ActionGraphResolver(BazelRunner bazelRunner, BazelData bazelData) { + this.bazelRunner = bazelRunner; + this.bazelData = bazelData; + } + + public ActionGraphParser getActionGraphParser(List targets, List languageIds) { + try { + BazelProcess process = + bazelRunner + .commandBuilder() + .aquery() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withMnemonic(targets, languageIds) + .executeBazelBesCommand(); + + if (bazelData.getVersion().compareTo(ACTION_GRAPH_V2_VERSION) < 0) { + AnalysisProtos.ActionGraphContainer actionGraphContainer = + AnalysisProtos.ActionGraphContainer.parseFrom(process.getInputStream()); + return new ActionGraphV1Parser(actionGraphContainer); + } else { + AnalysisProtosV2.ActionGraphContainer actionGraphContainer = + AnalysisProtosV2.ActionGraphContainer.parseFrom(process.getInputStream()); + return new ActionGraphV2Parser(actionGraphContainer); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/BUILD new file mode 100644 index 000000000..2c3f6bffa --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/BUILD @@ -0,0 +1,24 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "resolvers", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/data", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/params", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils", + "@io_bazel//src/main/protobuf:analysis_java_proto", + "@io_bazel//src/main/protobuf:analysis_v2_java_proto", + "@io_bazel//src/main/protobuf:build_java_proto", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_guava_guava", + "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/QueryResolver.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/QueryResolver.java new file mode 100644 index 000000000..6683eb383 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/QueryResolver.java @@ -0,0 +1,16 @@ +package org.jetbrains.bsp.bazel.server.bsp.resolvers; + +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import java.io.IOException; +import org.jetbrains.bsp.bazel.server.bazel.BazelProcess; + +public class QueryResolver { + + public static Build.QueryResult getQueryResultForProcess(BazelProcess process) { + try { + return Build.QueryResult.parseFrom(process.getInputStream()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/TargetRulesResolver.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/TargetRulesResolver.java new file mode 100644 index 000000000..e330dc94b --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/TargetRulesResolver.java @@ -0,0 +1,63 @@ +package org.jetbrains.bsp.bazel.server.bsp.resolvers; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import com.google.devtools.build.lib.query2.proto.proto2api.Build.Rule; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.jetbrains.bsp.bazel.server.bazel.BazelProcess; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; + +public class TargetRulesResolver { + + private final BazelRunner bazelRunner; + + private final Predicate filter; + private final Function mapper; + + private TargetRulesResolver( + BazelRunner bazelRunner, Predicate filter, Function mapper) { + this.bazelRunner = bazelRunner; + this.filter = filter; + this.mapper = mapper; + } + + public static TargetRulesResolver withBazelRunnerAndMapper( + BazelRunner bazelRunner, Function mapper) { + return withBazelRunnerAndFilterAndMapper(bazelRunner, o -> true, mapper); + } + + public static TargetRulesResolver withBazelRunnerAndFilterAndMapper( + BazelRunner bazelRunner, Predicate filter, Function mapper) { + return new TargetRulesResolver(bazelRunner, filter, mapper); + } + + public List getItemsForTargets(List targetsIds) { + Build.QueryResult queryResult = getBuildQueryResult(targetsIds); + + return queryResult.getTargetList().stream() + .map(Build.Target::getRule) + .filter(filter) + .map(mapper) + .collect(Collectors.toList()); + } + + private Build.QueryResult getBuildQueryResult(List targetsIds) { + List targets = TargetsUtils.getTargetsUris(targetsIds); + BazelProcess bazelProcess = queryBazel(targets); + + return QueryResolver.getQueryResultForProcess(bazelProcess); + } + + private BazelProcess queryBazel(List targets) { + return bazelRunner + .commandBuilder() + .query() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withTargets(targets) + .executeBazelBesCommand(); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/TargetsLanguageOptionsResolver.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/TargetsLanguageOptionsResolver.java new file mode 100644 index 000000000..ffb386268 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/TargetsLanguageOptionsResolver.java @@ -0,0 +1,178 @@ +package org.jetbrains.bsp.bazel.server.bsp.resolvers; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.commons.Uri; +import org.jetbrains.bsp.bazel.server.bazel.BazelProcess; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.actiongraph.ActionGraphParser; + +public class TargetsLanguageOptionsResolver { + + private static final List ACTION_GRAPH_SUFFIXES = ImmutableList.of(".jar", ".js"); + + private final BazelData bazelData; + private final BazelRunner bazelRunner; + private final ActionGraphResolver actionGraphResolver; + private final String compilerOptionsName; + private final List languagesIds; + private final ResultItemsCollector resultItemsCollector; + + private TargetsLanguageOptionsResolver( + BazelData bazelData, + BazelRunner bazelRunner, + String compilerOptionsName, + List languagesIds, + ResultItemsCollector resultItemsCollector) { + this.bazelData = bazelData; + this.bazelRunner = bazelRunner; + this.compilerOptionsName = compilerOptionsName; + this.languagesIds = languagesIds; + this.resultItemsCollector = resultItemsCollector; + + this.actionGraphResolver = new ActionGraphResolver(bazelRunner, bazelData); + } + + public static Builder builder() { + return new Builder<>(); + } + + public List getResultItemsForTargets(List buildTargetsIdentifiers) { + List targets = TargetsUtils.getTargetsUris(buildTargetsIdentifiers); + ActionGraphParser actionGraphParser = + actionGraphResolver.getActionGraphParser(targets, languagesIds); + + return targets.stream() + .flatMap(target -> getResultItems(target, targets, actionGraphParser)) + .collect(Collectors.toList()); + } + + private Stream getResultItems( + String target, List allTargets, ActionGraphParser actionGraphParser) { + Map> targetsOptions = getTargetsOptions(allTargets); + + return getResultItemForActionGraphParserOptionsTargetsOptionsAndTarget( + actionGraphParser, targetsOptions, target); + } + + private Map> getTargetsOptions(List targets) { + BazelProcess bazelProcess = queryBazel(targets); + + Build.QueryResult query = QueryResolver.getQueryResultForProcess(bazelProcess); + + return query.getTargetList().stream() + .map(Build.Target::getRule) + .collect(Collectors.toMap(Build.Rule::getName, this::collectRules)); + } + + private BazelProcess queryBazel(List targets) { + return bazelRunner + .commandBuilder() + .query() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withTargets(targets) + .executeBazelBesCommand(); + } + + private List collectRules(Build.Rule rule) { + return rule.getAttributeList().stream() + .filter(this::isAttributeCompilerOptionsName) + .flatMap(attr -> attr.getStringListValueList().stream()) + .collect(Collectors.toList()); + } + + private boolean isAttributeCompilerOptionsName(Build.Attribute attribute) { + return attribute.getName().equals(compilerOptionsName); + } + + private Stream getResultItemForActionGraphParserOptionsTargetsOptionsAndTarget( + ActionGraphParser actionGraphParser, + Map> targetsOptions, + String target) { + + BuildTargetIdentifier targetIdentifier = new BuildTargetIdentifier(target); + List options = targetsOptions.getOrDefault(target, ImmutableList.of()); + List inputs = actionGraphParser.getInputsAsUri(target, bazelData.getExecRoot()); + + return actionGraphParser.getOutputs(target, ACTION_GRAPH_SUFFIXES).stream() + .map(this::mapActionGraphOutputsToClassDirectory) + .map( + classDirectory -> + resultItemsCollector.apply(targetIdentifier, options, inputs, classDirectory)); + } + + private String mapActionGraphOutputsToClassDirectory(String output) { + String execPath = Constants.EXEC_ROOT_PREFIX + output; + + return Uri.fromExecPath(execPath, bazelData.getExecRoot()).toString(); + } + + @FunctionalInterface + public interface ResultItemsCollector { + + T apply( + BuildTargetIdentifier target, + List options, + List classpath, + String classDirectory); + } + + public static class Builder { + + private BazelData bazelData; + private BazelRunner bazelRunner; + private String compilerOptionsName; + private List languagesIds; + private ResultItemsCollector resultItemsCollector; + + public Builder bazelData(BazelData bazelData) { + this.bazelData = bazelData; + return this; + } + + public Builder bazelRunner(BazelRunner bazelRunner) { + this.bazelRunner = bazelRunner; + return this; + } + + public Builder compilerOptionsName(String compilerOptionsName) { + this.compilerOptionsName = compilerOptionsName; + return this; + } + + public Builder languagesIds(List languagesIds) { + this.languagesIds = languagesIds; + return this; + } + + public Builder resultItemsCollector(ResultItemsCollector resultItemsCollector) { + this.resultItemsCollector = resultItemsCollector; + return this; + } + + public TargetsLanguageOptionsResolver build() { + throwExceptionIfAnyFieldIsNotFilled(); + + return new TargetsLanguageOptionsResolver( + bazelData, bazelRunner, compilerOptionsName, languagesIds, resultItemsCollector); + } + + private void throwExceptionIfAnyFieldIsNotFilled() { + if (bazelData == null + || bazelRunner == null + || compilerOptionsName == null + || languagesIds == null + || resultItemsCollector == null) { + throw new IllegalStateException("Every TargetsResolver.Builder field has to be set"); + } + } + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/TargetsUtils.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/TargetsUtils.java new file mode 100644 index 000000000..9684c6d0f --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/TargetsUtils.java @@ -0,0 +1,59 @@ +package org.jetbrains.bsp.bazel.server.bsp.resolvers; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import com.google.common.base.Joiner; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import java.util.List; +import java.util.stream.Collectors; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.TargetsSection; + +public final class TargetsUtils { + + public static List getTargetsUris(List targets) { + return targets.stream().map(BuildTargetIdentifier::getUri).collect(Collectors.toList()); + } + + public static boolean isAttributeSpecifiedAndHasGivenName( + Build.Attribute attribute, String name) { + boolean hasGivenName = attribute.getName().equals(name); + + return hasGivenName && attribute.hasExplicitlySpecified() && attribute.getExplicitlySpecified(); + } + + public static String getTargetsUnion(List targets) { + return targets.stream().map(BuildTargetIdentifier::getUri).collect(Collectors.joining("+")); + } + + public static String getKindInput(ProjectView projectView, String fileUri, String prefix) { + return String.format( + "rdeps(%s, %s, 1)", + TargetsUtils.getAllProjectTargetsWithExcludedTargets(projectView), + fileUri.substring(prefix.length())); + } + + public static String getAllProjectTargetsWithExcludedTargets(ProjectView projectView) { + TargetsSection targetsSection = projectView.getTargets(); + String excludedTargets = getExcludedTargets(targetsSection.getExcludedTargets()); + String includedTargets = Joiner.on(" ").join(targetsSection.getIncludedTargets()); + + return String.format("%s %s", includedTargets, excludedTargets); + } + + public static String getTargetWithExcludedTargets(ProjectView projectView, String target) { + TargetsSection targetsSection = projectView.getTargets(); + String excludedTargets = getExcludedTargets(targetsSection.getExcludedTargets()); + + return String.format("%s %s", target, excludedTargets); + } + + private static String getExcludedTargets(List excludedTargets) { + return excludedTargets.stream() + .map(TargetsUtils::addExceptStatement) + .collect(Collectors.joining(" ")); + } + + private static String addExceptStatement(String target) { + return String.format("except %s", target); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/ActionGraphParser.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/ActionGraphParser.java new file mode 100644 index 000000000..680c63a1b --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/ActionGraphParser.java @@ -0,0 +1,32 @@ +package org.jetbrains.bsp.bazel.server.bsp.resolvers.actiongraph; + +import com.google.common.collect.Lists; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.jetbrains.bsp.bazel.commons.Uri; + +public abstract class ActionGraphParser { + + // Obtains all of the files outputted by this target that match a list of suffixes + public abstract List getOutputs(String target, List suffixes); + + // Obtains all of the dependencies, or inputs, of this target that match a list of suffixes + protected abstract Stream buildInputs(String target, List suffixes); + + public static final String EXEC_ROOT = "exec-root://"; + + protected List getInputs(String target, List suffixes) { + return buildInputs(target, suffixes) + .filter(path -> suffixes.stream().anyMatch(path::endsWith)) + .distinct() + .collect(Collectors.toList()); + } + + // Obtains all of the dependencies, or inputs, of this target and converts them to Uri form + public List getInputsAsUri(String target, String execRoot) { + return getInputs(target, Lists.newArrayList(".jar", "js")).stream() + .map(execPath -> Uri.fromExecPath(execPath, execRoot).toString()) + .collect(Collectors.toList()); + } +} diff --git a/main/src/org/jetbrains/bsp/bazel/ActionGraphParser.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/ActionGraphV1Parser.java similarity index 56% rename from main/src/org/jetbrains/bsp/bazel/ActionGraphParser.java rename to src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/ActionGraphV1Parser.java index 676e40dbe..ad9281a92 100644 --- a/main/src/org/jetbrains/bsp/bazel/ActionGraphParser.java +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/ActionGraphV1Parser.java @@ -1,4 +1,4 @@ -package org.jetbrains.bsp.bazel; +package org.jetbrains.bsp.bazel.server.bsp.resolvers.actiongraph; import com.google.common.collect.Lists; import com.google.devtools.build.lib.analysis.AnalysisProtos; @@ -7,39 +7,26 @@ import java.util.List; import java.util.Queue; import java.util.Set; -import java.util.TreeSet; import java.util.stream.Collectors; +import java.util.stream.Stream; // TODO(illicitonion): Index, cache, etc -public class ActionGraphParser { +public class ActionGraphV1Parser extends ActionGraphParser { private final AnalysisProtos.ActionGraphContainer actionGraph; - public ActionGraphParser(AnalysisProtos.ActionGraphContainer actionGraph) { + public ActionGraphV1Parser(AnalysisProtos.ActionGraphContainer actionGraph) { this.actionGraph = actionGraph; } - public List getInputs(String target, List suffixes) { + @Override + protected Stream buildInputs(String target, List suffixes) { return getActions(target).stream() .flatMap(action -> action.getInputDepSetIdsList().stream()) - .flatMap( - depset -> { - Queue queue = new ArrayDeque<>(); - queue.add(depset); - return expandDepsetToArtifacts(queue).stream(); - }) - .map(artifact -> "exec-root://" + artifact.getExecPath()) - .filter(path -> suffixes.stream().anyMatch(path::endsWith)) - .collect(Collectors.toCollection(TreeSet::new)) - .stream() - .collect(Collectors.toList()); - } - - public List getInputsAsUri(String target, String execRoot) { - return getInputs(target, Lists.newArrayList(".jar", "js")).stream() - .map(exec_path -> Uri.fromExecPath(exec_path, execRoot).toString()) - .collect(Collectors.toList()); + .flatMap(depset -> expandDepsetToArtifacts(depset).stream()) + .map(artifact -> EXEC_ROOT + artifact.getExecPath()); } + @Override public List getOutputs(String target, List suffixes) { Set artifactIds = getActions(target).stream() @@ -52,9 +39,9 @@ public List getOutputs(String target, List suffixes) { .collect(Collectors.toList()); } - private String getTargetId(String needle) { + private String getTargetId(String targetLabel) { return actionGraph.getTargetsList().stream() - .filter(target -> needle.equals(target.getLabel())) + .filter(target -> targetLabel.equals(target.getLabel())) .findFirst() .orElse(AnalysisProtos.Target.newBuilder().build()) .getId(); @@ -67,23 +54,26 @@ private List getActions(String targetLabel) { .collect(Collectors.toList()); } - private List expandDepsetToArtifacts(Queue idsToExpand) { - HashSet expandedIds = new HashSet<>(); + private List expandDepsetToArtifacts(String idToExpand) { + Queue idsToExpand = new ArrayDeque<>(Lists.newArrayList(idToExpand)); + + Set expandedIds = new HashSet<>(); - HashSet artifactIds = new HashSet<>(); + Set artifactIds = new HashSet<>(); while (!idsToExpand.isEmpty()) { String depsetId = idsToExpand.remove(); if (expandedIds.contains(depsetId)) { continue; } expandedIds.add(depsetId); - for (AnalysisProtos.DepSetOfFiles depset : actionGraph.getDepSetOfFilesList()) { - if (!depsetId.equals(depset.getId())) { - continue; - } - idsToExpand.addAll(depset.getTransitiveDepSetIdsList()); - artifactIds.addAll(depset.getDirectArtifactIdsList()); - } + + actionGraph.getDepSetOfFilesList().stream() + .filter(depset -> depsetId.equals(depset.getId())) + .forEach( + depset -> { + idsToExpand.addAll(depset.getTransitiveDepSetIdsList()); + artifactIds.addAll(depset.getDirectArtifactIdsList()); + }); } return actionGraph.getArtifactsList().stream() .filter(artifact -> artifactIds.contains(artifact.getId())) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/ActionGraphV2Parser.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/ActionGraphV2Parser.java new file mode 100644 index 000000000..771ea40cf --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/ActionGraphV2Parser.java @@ -0,0 +1,107 @@ +package org.jetbrains.bsp.bazel.server.bsp.resolvers.actiongraph; + +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.analysis.AnalysisProtosV2; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ActionGraphV2Parser extends ActionGraphParser { + private final AnalysisProtosV2.ActionGraphContainer actionGraph; + private final Map pathFragmentMap; + + public ActionGraphV2Parser(AnalysisProtosV2.ActionGraphContainer actionGraph) { + this.actionGraph = actionGraph; + pathFragmentMap = + actionGraph.getPathFragmentsList().stream() + .collect(Collectors.toMap(AnalysisProtosV2.PathFragment::getId, fragment -> fragment)); + } + + @Override + protected Stream buildInputs(String target, List suffixes) { + return getActions(target).stream() + .flatMap(action -> action.getInputDepSetIdsList().stream()) + .flatMap(depset -> expandDepsetToArtifacts(depset).stream()) + .map(AnalysisProtosV2.Artifact::getPathFragmentId) + .map(pathFragmentId -> EXEC_ROOT + constructPath(pathFragmentId)); + } + + private String constructPath(Integer pathFragmentId) { + int currId = pathFragmentId; + List pathBuilder = new ArrayList<>(); + while (currId != 0) { + AnalysisProtosV2.PathFragment fragment = pathFragmentMap.get(currId); + pathBuilder.add(fragment.getLabel()); + currId = fragment.getParentId(); + } + + Collections.reverse(pathBuilder); + return String.join("/", pathBuilder); + } + + @Override + public List getOutputs(String target, List suffixes) { + Set artifactIds = + getActions(target).stream() + .flatMap(action -> action.getOutputIdsList().stream()) + .collect(Collectors.toSet()); + return actionGraph.getArtifactsList().stream() + .filter(artifact -> artifactIds.contains(artifact.getId())) + .map(AnalysisProtosV2.Artifact::getPathFragmentId) + .map(this::constructPath) + .filter(path -> suffixes.stream().anyMatch(path::endsWith)) + .collect(Collectors.toList()); + } + + private int findTargetIdForLabel(String targetLabel) { + return actionGraph.getTargetsList().stream() + .filter(target -> targetLabel.equals(target.getLabel())) + .findFirst() + .orElse(AnalysisProtosV2.Target.newBuilder().build()) + .getId(); + } + + private List getActions(String targetLabel) { + int targetId = findTargetIdForLabel(targetLabel); + return actionGraph.getActionsList().stream() + .filter(action -> targetId == action.getTargetId()) + .collect(Collectors.toList()); + } + + // From a given set of dependencies of a target, expand their dependency graph and their + // transitive dependencies + // This way, all of the Artifacts - the representation of source files or derived output files - + // that that target depends on are obtained + private List expandDepsetToArtifacts(Integer idToExpand) { + Queue idsToExpand = new ArrayDeque<>(Lists.newArrayList(idToExpand)); + + Set expandedIds = new HashSet<>(); + + Set artifactIds = new HashSet<>(); + while (!idsToExpand.isEmpty()) { + Integer depsetId = idsToExpand.remove(); + if (expandedIds.contains(depsetId)) { + continue; + } + expandedIds.add(depsetId); + + actionGraph.getDepSetOfFilesList().stream() + .filter(depset -> depsetId.equals(depset.getId())) + .forEach( + depset -> { + idsToExpand.addAll(depset.getTransitiveDepSetIdsList()); + artifactIds.addAll(depset.getDirectArtifactIdsList()); + }); + } + return actionGraph.getArtifactsList().stream() + .filter(artifact -> artifactIds.contains(artifact.getId())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/BUILD new file mode 100644 index 000000000..8201ff3f3 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers/actiongraph/BUILD @@ -0,0 +1,21 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "actiongraph", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/data", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/params", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/utils", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils", + "@io_bazel//src/main/protobuf:analysis_java_proto", + "@io_bazel//src/main/protobuf:analysis_v2_java_proto", + "@io_bazel//src/main/protobuf:build_java_proto", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_guava_guava", + "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/BUILD new file mode 100644 index 000000000..6b2880824 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/BUILD @@ -0,0 +1,26 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "services", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/data", + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/params", + "//src/main/java/org/jetbrains/bsp/bazel/server/bep", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/managers", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/resolvers", + "//src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils", + "@io_bazel//src/main/protobuf:build_java_proto", + "@io_bazel//third_party/grpc:grpc-jar", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_guava_guava", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + "@maven//:org_eclipse_lsp4j_org_eclipse_lsp4j_jsonrpc", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/BuildServerService.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/BuildServerService.java new file mode 100644 index 000000000..00602a519 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/BuildServerService.java @@ -0,0 +1,395 @@ +package org.jetbrains.bsp.bazel.server.bsp.services; + +import ch.epfl.scala.bsp4j.BuildServerCapabilities; +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.CleanCacheParams; +import ch.epfl.scala.bsp4j.CleanCacheResult; +import ch.epfl.scala.bsp4j.CompileParams; +import ch.epfl.scala.bsp4j.CompileProvider; +import ch.epfl.scala.bsp4j.CompileResult; +import ch.epfl.scala.bsp4j.DependencySourcesItem; +import ch.epfl.scala.bsp4j.DependencySourcesParams; +import ch.epfl.scala.bsp4j.DependencySourcesResult; +import ch.epfl.scala.bsp4j.InitializeBuildParams; +import ch.epfl.scala.bsp4j.InitializeBuildResult; +import ch.epfl.scala.bsp4j.InverseSourcesParams; +import ch.epfl.scala.bsp4j.InverseSourcesResult; +import ch.epfl.scala.bsp4j.ResourcesItem; +import ch.epfl.scala.bsp4j.ResourcesParams; +import ch.epfl.scala.bsp4j.ResourcesResult; +import ch.epfl.scala.bsp4j.RunParams; +import ch.epfl.scala.bsp4j.RunProvider; +import ch.epfl.scala.bsp4j.RunResult; +import ch.epfl.scala.bsp4j.SourceItem; +import ch.epfl.scala.bsp4j.SourcesItem; +import ch.epfl.scala.bsp4j.SourcesParams; +import ch.epfl.scala.bsp4j.SourcesResult; +import ch.epfl.scala.bsp4j.StatusCode; +import ch.epfl.scala.bsp4j.TestParams; +import ch.epfl.scala.bsp4j.TestProvider; +import ch.epfl.scala.bsp4j.TestResult; +import ch.epfl.scala.bsp4j.WorkspaceBuildTargetsResult; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.commons.Lazy; +import org.jetbrains.bsp.bazel.commons.Uri; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; +import org.jetbrains.bsp.bazel.server.bazel.BazelProcess; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelProcessResult; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelQueryKindParameters; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerBuildManager; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerLifetime; +import org.jetbrains.bsp.bazel.server.bsp.BazelBspServerRequestHelpers; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.QueryResolver; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.TargetRulesResolver; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.TargetsUtils; + +public class BuildServerService { + + private static final Logger LOGGER = LogManager.getLogger(BuildServerService.class); + + private final BazelBspServerRequestHelpers serverRequestHelpers; + private final BazelBspServerLifetime serverLifetime; + private final BazelBspServerBuildManager serverBuildManager; + + private final BazelData bazelData; + private final BazelRunner bazelRunner; + private final ProjectView projectView; + + public BuildServerService( + BazelBspServerRequestHelpers serverRequestHelpers, + BazelBspServerLifetime serverLifetime, + BazelBspServerBuildManager serverBuildManager, + BazelData bazelData, + BazelRunner bazelRunner, + ProjectView projectView) { + this.serverRequestHelpers = serverRequestHelpers; + this.serverLifetime = serverLifetime; + this.serverBuildManager = serverBuildManager; + this.bazelData = bazelData; + this.bazelRunner = bazelRunner; + this.projectView = projectView; + } + + public CompletableFuture buildInitialize( + InitializeBuildParams initializeBuildParams) { + LOGGER.info("buildInitialize call with param: {}", initializeBuildParams); + + return processBuildInitialize("buildInitialize", this::handleBuildInitialize); + } + + private CompletableFuture processBuildInitialize( + String methodName, Supplier> request) { + if (serverLifetime.isFinished()) { + return serverRequestHelpers.completeExceptionally( + methodName, + new ResponseError( + ResponseErrorCode.serverErrorEnd, "Server has already shutdown!", false)); + } + + return serverRequestHelpers.getValue(methodName, request); + } + + private Either handleBuildInitialize() { + BuildServerCapabilities capabilities = new BuildServerCapabilities(); + capabilities.setCompileProvider(new CompileProvider(Constants.SUPPORTED_LANGUAGES)); + capabilities.setRunProvider(new RunProvider(Constants.SUPPORTED_LANGUAGES)); + capabilities.setTestProvider(new TestProvider(Constants.SUPPORTED_LANGUAGES)); + capabilities.setDependencySourcesProvider(true); + capabilities.setInverseSourcesProvider(true); + capabilities.setResourcesProvider(true); + return Either.forRight( + new InitializeBuildResult( + Constants.NAME, Constants.VERSION, Constants.BSP_VERSION, capabilities)); + } + + public void onBuildInitialized() { + LOGGER.info("onBuildInitialized call"); + + serverLifetime.setInitializedComplete(); + } + + public CompletableFuture buildShutdown() { + LOGGER.info("buildShutdown call"); + + return processBuildShutdown("buildShutdown", this::handleBuildShutdown); + } + + private CompletableFuture processBuildShutdown( + String methodName, Supplier> request) { + if (!serverLifetime.isInitialized()) { + return serverRequestHelpers.completeExceptionally( + methodName, + new ResponseError( + ResponseErrorCode.serverErrorEnd, "Server has not been initialized yet!", false)); + } + + return serverRequestHelpers.getValue(methodName, request); + } + + private Either handleBuildShutdown() { + serverLifetime.setFinishedComplete(); + return Either.forRight(new Object()); + } + + public void onBuildExit() { + LOGGER.info("onBuildExit call"); + + serverLifetime.forceFinish(); + } + + public CompletableFuture workspaceBuildTargets() { + LOGGER.info("workspaceBuildTargets call"); + + return serverBuildManager.getWorkspaceBuildTargets(); + } + + public Either buildTargetSources(SourcesParams sourcesParams) { + LOGGER.info("buildTargetSources call with param: {}", sourcesParams); + + TargetRulesResolver targetRulesResolver = + TargetRulesResolver.withBazelRunnerAndMapper(bazelRunner, this::mapBuildRuleToSourcesItem); + + List sourceItems = + targetRulesResolver.getItemsForTargets(sourcesParams.getTargets()); + + SourcesResult sourcesResult = new SourcesResult(sourceItems); + + return Either.forRight(sourcesResult); + } + + private SourcesItem mapBuildRuleToSourcesItem(Build.Rule rule) { + BuildTargetIdentifier ruleLabel = new BuildTargetIdentifier(rule.getName()); + List items = serverBuildManager.getSourceItems(rule, ruleLabel); + List roots = getRuleRoots(rule); + + return createSourcesForLabelAndItemsAndRoots(ruleLabel, items, roots); + } + + private List getRuleRoots(Build.Rule rule) { + String sourcesRootUriString = serverBuildManager.getSourcesRoot(rule.getName()); + Uri uri = Uri.fromAbsolutePath(sourcesRootUriString); + + return ImmutableList.of(uri.toString()); + } + + private SourcesItem createSourcesForLabelAndItemsAndRoots( + BuildTargetIdentifier label, List items, List roots) { + SourcesItem item = new SourcesItem(label, items); + item.setRoots(roots); + + return item; + } + + public Either buildTargetInverseSources( + InverseSourcesParams inverseSourcesParams) { + LOGGER.info("buildTargetInverseSources call with param: {}", inverseSourcesParams); + + String fileUri = inverseSourcesParams.getTextDocument().getUri(); + String workspaceRoot = bazelData.getWorkspaceRoot(); + String prefix = Uri.fromWorkspacePath("", workspaceRoot).toString(); + if (!inverseSourcesParams.getTextDocument().getUri().startsWith(prefix)) { + LOGGER.error("Could not resolve {} within workspace {}", fileUri, prefix); + + throw new RuntimeException("Could not resolve " + fileUri + " within workspace " + prefix); + } + String kindInput = TargetsUtils.getKindInput(projectView, fileUri, prefix); + BazelQueryKindParameters kindParameter = + BazelQueryKindParameters.fromPatternAndInput("rule", kindInput); + + BazelProcess bazelProcess = + bazelRunner + .commandBuilder() + .query() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withKind(kindParameter) + .executeBazelBesCommand(); + + Build.QueryResult result = QueryResolver.getQueryResultForProcess(bazelProcess); + + List targets = + result.getTargetList().stream() + .map(Build.Target::getRule) + .map(Build.Rule::getName) + .map(BuildTargetIdentifier::new) + .collect(Collectors.toList()); + + return Either.forRight(new InverseSourcesResult(targets)); + } + + public Either buildTargetDependencySources( + DependencySourcesParams dependencySourcesParams) { + LOGGER.info("buildTargetDependencySources call with param: {}", dependencySourcesParams); + + List targets = TargetsUtils.getTargetsUris(dependencySourcesParams.getTargets()); + + DependencySourcesResult result = + new DependencySourcesResult( + targets.stream() + .sorted() + .map( + target -> { + List files = + serverBuildManager.lookUpTransitiveSourceJars(target).stream() + .map( + execPath -> + Uri.fromExecPath(execPath, bazelData.getExecRoot()) + .toString()) + .collect(Collectors.toList()); + return new DependencySourcesItem(new BuildTargetIdentifier(target), files); + }) + .collect(Collectors.toList())); + + return Either.forRight(result); + } + + public Either buildTargetResources( + ResourcesParams resourcesParams) { + LOGGER.info("buildTargetResources call with param: {}", resourcesParams); + + BazelProcess bazelProcess = + bazelRunner + .commandBuilder() + .query() + .withFlag(BazelRunnerFlag.OUTPUT_PROTO) + .withArgument(TargetsUtils.getAllProjectTargetsWithExcludedTargets(projectView)) + .executeBazelBesCommand(); + + Build.QueryResult query = QueryResolver.getQueryResultForProcess(bazelProcess); + + LOGGER.info("Resources query result {}", query); + + ResourcesResult resourcesResult = + new ResourcesResult( + query.getTargetList().stream() + .map(Build.Target::getRule) + .filter( + rule -> + resourcesParams.getTargets().stream() + .anyMatch(target -> target.getUri().equals(rule.getName()))) + .filter( + rule -> + rule.getAttributeList().stream() + .anyMatch( + attribute -> + attribute.getName().equals("resources") + && attribute.hasExplicitlySpecified() + && attribute.getExplicitlySpecified())) + .map( + rule -> + new ResourcesItem( + new BuildTargetIdentifier(rule.getName()), + serverBuildManager.getResources(rule, query))) + .collect(Collectors.toList())); + + return Either.forRight(resourcesResult); + } + + public Either buildTargetCompile(CompileParams compileParams) { + LOGGER.info("buildTargetCompile call with param: {}", compileParams); + return serverBuildManager.buildTargetsWithBep(compileParams.getTargets(), new ArrayList<>()); + } + + public Either buildTargetTest(TestParams testParams) { + LOGGER.info("buildTargetTest call with param: {}", testParams); + + Either build = + serverBuildManager.buildTargetsWithBep( + Lists.newArrayList(testParams.getTargets()), new ArrayList<>()); + if (build.isLeft()) { + return Either.forLeft(build.getLeft()); + } + + CompileResult result = build.getRight(); + if (result.getStatusCode() != StatusCode.OK) { + return Either.forRight(new TestResult(result.getStatusCode())); + } + + List testTargets = TargetsUtils.getTargetsUris(testParams.getTargets()); + + BazelProcessResult bazelProcessResult = + bazelRunner + .commandBuilder() + .test() + .withTargets(testTargets) + .withArguments(testParams.getArguments()) + .executeBazelBesCommand() + .waitAndGetResult(); + + return Either.forRight(new TestResult(bazelProcessResult.getStatusCode())); + } + + public Either buildTargetRun(RunParams runParams) { + LOGGER.info("buildTargetRun call with param: {}", runParams); + + Either build = + serverBuildManager.buildTargetsWithBep( + Lists.newArrayList(runParams.getTarget()), new ArrayList<>()); + if (build.isLeft()) { + return Either.forLeft(build.getLeft()); + } + + CompileResult result = build.getRight(); + if (result.getStatusCode() != StatusCode.OK) { + return Either.forRight(new RunResult(result.getStatusCode())); + } + + BazelProcessResult bazelProcessResult = + bazelRunner + .commandBuilder() + .run() + .withArgument(runParams.getTarget().getUri()) + .withArguments(runParams.getArguments()) + .executeBazelBesCommand() + .waitAndGetResult(); + + return Either.forRight(new RunResult(bazelProcessResult.getStatusCode())); + } + + public Either buildTargetCleanCache( + CleanCacheParams cleanCacheParams) { + LOGGER.info("buildTargetCleanCache call with param: {}", cleanCacheParams); + + CleanCacheResult result; + try { + List lines = + bazelRunner + .commandBuilder() + .clean() + .executeBazelBesCommand() + .waitAndGetResult() + .getStdout(); + + result = new CleanCacheResult(String.join("\n", lines), true); + } catch (RuntimeException e) { + // TODO does it make sense to return a successful response here? + // If we caught an exception here, there was an internal server error... + result = new CleanCacheResult(e.getMessage(), false); + } + return Either.forRight(result); + } + + public Either workspaceReload() { + LOGGER.info("workspaceReload call"); + + bazelRunner.commandBuilder().fetch().executeBazelBesCommand().waitAndGetResult(); + serverBuildManager.getLazyVals().forEach(Lazy::recalculateValue); + + return Either.forRight(new Object()); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/CppBuildServerService.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/CppBuildServerService.java new file mode 100644 index 000000000..7e46cd72a --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/CppBuildServerService.java @@ -0,0 +1,82 @@ +package org.jetbrains.bsp.bazel.server.bsp.services; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.CppOptionsItem; +import ch.epfl.scala.bsp4j.CppOptionsParams; +import ch.epfl.scala.bsp4j.CppOptionsResult; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.params.BazelRunnerFlag; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspAspectsManager; + +public class CppBuildServerService { + public static final int COPTS_LOCATION = 0; + public static final int DEFINES_LOCATION = 1; + public static final int LINKOPTS_LOCATION = 2; + public static final int LINKSHARED_LOCATION = 3; + private final BazelRunner bazelRunner; + private static final String FETCH_CPP_TARGET_ASPECT = + "@//.bazelbsp:aspects.bzl%get_cpp_target_info"; + + public CppBuildServerService(BazelRunner bazelRunner) { + this.bazelRunner = bazelRunner; + } + + public Either buildTargetCppOptions( + CppOptionsParams cppOptionsParams) { + List items = + cppOptionsParams.getTargets().stream().map(this::getOptions).collect(Collectors.toList()); + CppOptionsResult cppOptionsResult = new CppOptionsResult(items); + return Either.forRight(cppOptionsResult); + } + + private CppOptionsItem getOptions(BuildTargetIdentifier buildTargetIdentifier) { + List lines = + bazelRunner + .commandBuilder() + .build() + .withFlag(BazelRunnerFlag.ASPECTS, FETCH_CPP_TARGET_ASPECT) + .withArgument(buildTargetIdentifier.getUri()) + .executeBazelBesCommand() + .getStderr(); + + List targetInfo = + lines.stream() + .map(line -> Splitter.on(" ").splitToList(line)) + .filter( + parts -> + parts.size() == 3 + && parts.get(0).equals(BazelBspAspectsManager.DEBUG_MESSAGE) + && parts.get(1).contains(BazelBspAspectsManager.ASPECT_LOCATION)) + .map(parts -> parts.get(2)) + .collect(Collectors.toList()); + + if (targetInfo.size() != 4) { + return new CppOptionsItem( + buildTargetIdentifier, ImmutableList.of(), ImmutableList.of(), ImmutableList.of()); + } else { + List copts = + Arrays.stream(targetInfo.get(COPTS_LOCATION).split(",")).collect(Collectors.toList()); + List defines = + Arrays.stream(targetInfo.get(DEFINES_LOCATION).split(",")).collect(Collectors.toList()); + List linkopts = + Arrays.stream(targetInfo.get(LINKOPTS_LOCATION).split(",")).collect(Collectors.toList()); + + boolean linkshared = false; + if (targetInfo.get(LINKSHARED_LOCATION).equals("True")) { + linkshared = true; + } + + CppOptionsItem cppOptionsItem = + new CppOptionsItem(buildTargetIdentifier, copts, defines, linkopts); + cppOptionsItem.setLinkshared(linkshared); + return cppOptionsItem; + } + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/JavaBuildServerService.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/JavaBuildServerService.java new file mode 100644 index 000000000..d1fd81806 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/JavaBuildServerService.java @@ -0,0 +1,66 @@ +package org.jetbrains.bsp.bazel.server.bsp.services; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.JavacOptionsItem; +import ch.epfl.scala.bsp4j.JavacOptionsParams; +import ch.epfl.scala.bsp4j.JavacOptionsResult; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspCompilationManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspQueryManager; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.TargetsLanguageOptionsResolver; + +public class JavaBuildServerService { + + private static final Logger LOGGER = LogManager.getLogger(JavaBuildServerService.class); + + private static final String JAVA_COMPILER_OPTIONS_NAME = "javacopts"; + // TODO(andrefmrocha): Remove this when kotlin is natively supported + private static final List JAVA_LANGUAGES_IDS = + ImmutableList.of(Constants.JAVAC, Constants.KOTLINC); + + private final BazelBspCompilationManager bazelBspCompilationManager; + private final BazelBspQueryManager bazelBspQueryManager; + + private final TargetsLanguageOptionsResolver targetsLanguageOptionsResolver; + + public JavaBuildServerService( + BazelBspCompilationManager bazelBspCompilationManager, + BazelBspQueryManager bazelBspQueryManager, + BazelData bazelData, + BazelRunner bazelRunner) { + this.bazelBspCompilationManager = bazelBspCompilationManager; + this.bazelBspQueryManager = bazelBspQueryManager; + this.targetsLanguageOptionsResolver = + TargetsLanguageOptionsResolver.builder() + .bazelData(bazelData) + .bazelRunner(bazelRunner) + .compilerOptionsName(JAVA_COMPILER_OPTIONS_NAME) + .languagesIds(JAVA_LANGUAGES_IDS) + .resultItemsCollector(JavacOptionsItem::new) + .build(); + } + + public Either buildTargetJavacOptions( + JavacOptionsParams javacOptionsParams) { + LOGGER.info("buildTargetJavacOptions call with param: {}", javacOptionsParams); + + // Make sure dependencies are cached + List dependenciesTargets = + bazelBspQueryManager.getTargetIdentifiersForDependencies(javacOptionsParams.getTargets()); + bazelBspCompilationManager.buildTargetsWithBep(dependenciesTargets, ImmutableList.of()); + + List resultItems = + targetsLanguageOptionsResolver.getResultItemsForTargets(javacOptionsParams.getTargets()); + + JavacOptionsResult javacOptionsResult = new JavacOptionsResult(resultItems); + return Either.forRight(javacOptionsResult); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/ScalaBuildServerService.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/ScalaBuildServerService.java new file mode 100644 index 000000000..e1ab2219f --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/services/ScalaBuildServerService.java @@ -0,0 +1,169 @@ +package org.jetbrains.bsp.bazel.server.bsp.services; + +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.ScalaMainClass; +import ch.epfl.scala.bsp4j.ScalaMainClassesItem; +import ch.epfl.scala.bsp4j.ScalaMainClassesParams; +import ch.epfl.scala.bsp4j.ScalaMainClassesResult; +import ch.epfl.scala.bsp4j.ScalaTestClassesItem; +import ch.epfl.scala.bsp4j.ScalaTestClassesParams; +import ch.epfl.scala.bsp4j.ScalaTestClassesResult; +import ch.epfl.scala.bsp4j.ScalacOptionsItem; +import ch.epfl.scala.bsp4j.ScalacOptionsParams; +import ch.epfl.scala.bsp4j.ScalacOptionsResult; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.query2.proto.proto2api.Build; +import com.google.devtools.build.lib.query2.proto.proto2api.Build.Attribute; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; +import org.jetbrains.bsp.bazel.commons.Constants; +import org.jetbrains.bsp.bazel.server.bazel.BazelRunner; +import org.jetbrains.bsp.bazel.server.bazel.data.BazelData; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspCompilationManager; +import org.jetbrains.bsp.bazel.server.bsp.managers.BazelBspQueryManager; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.TargetRulesResolver; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.TargetsLanguageOptionsResolver; +import org.jetbrains.bsp.bazel.server.bsp.resolvers.TargetsUtils; + +public class ScalaBuildServerService { + + private static final Logger LOGGER = LogManager.getLogger(JavaBuildServerService.class); + + private static final String SCALA_COMPILER_OPTIONS_ATTR_NAME = "scalacopts"; + private static final List SCALA_LANGUAGES_IDS = + ImmutableList.of(Constants.SCALAC, Constants.JAVAC); + + private static final String SCALA_TEST_RULE_CLASS_NAME = "scala_test"; + + private final TargetsLanguageOptionsResolver targetsLanguageOptionsResolver; + private final TargetRulesResolver targetsScalaMainClassesRulesResolver; + private final TargetRulesResolver targetsScalaTestClassesRulesResolver; + private final BazelBspCompilationManager bazelBspCompilationManager; + private final BazelBspQueryManager bazelBspQueryManager; + + public ScalaBuildServerService( + BazelData bazelData, + BazelRunner bazelRunner, + BazelBspCompilationManager bazelBspCompilationManager, + BazelBspQueryManager bazelBspQueryManager) { + this.bazelBspCompilationManager = bazelBspCompilationManager; + this.bazelBspQueryManager = bazelBspQueryManager; + this.targetsLanguageOptionsResolver = + TargetsLanguageOptionsResolver.builder() + .bazelData(bazelData) + .bazelRunner(bazelRunner) + .compilerOptionsName(SCALA_COMPILER_OPTIONS_ATTR_NAME) + .languagesIds(SCALA_LANGUAGES_IDS) + .resultItemsCollector(ScalacOptionsItem::new) + .build(); + + this.targetsScalaMainClassesRulesResolver = + TargetRulesResolver.withBazelRunnerAndMapper(bazelRunner, this::mapRuleToMainClassesItem); + + this.targetsScalaTestClassesRulesResolver = + TargetRulesResolver.withBazelRunnerAndFilterAndMapper( + bazelRunner, this::isScalaTestRule, this::mapRuleToTestClassesItem); + } + + private boolean isScalaTestRule(Build.Rule rule) { + return rule.getRuleClass().equals(SCALA_TEST_RULE_CLASS_NAME); + } + + private ScalaTestClassesItem mapRuleToTestClassesItem(Build.Rule rule) { + BuildTargetIdentifier target = new BuildTargetIdentifier(rule.getName()); + List classes = getTestMainClasses(rule); + + return new ScalaTestClassesItem(target, classes); + } + + private List getTestMainClasses(Build.Rule rule) { + return getAttribute(rule, Constants.SCALA_TEST_MAIN_CLASSES_ATTRIBUTE_NAME) + .map(Attribute::getStringValue) + .collect(Collectors.toList()); + } + + public Either buildTargetScalacOptions( + ScalacOptionsParams scalacOptionsParams) { + LOGGER.info("buildTargetScalacOptions call with param: {}", scalacOptionsParams); + + // Make sure dependencies are cached + List dependenciesTargets = + bazelBspQueryManager.getTargetIdentifiersForDependencies(scalacOptionsParams.getTargets()); + bazelBspCompilationManager.buildTargetsWithBep(dependenciesTargets, ImmutableList.of()); + + List resultItems = + targetsLanguageOptionsResolver.getResultItemsForTargets(scalacOptionsParams.getTargets()); + + ScalacOptionsResult scalacOptionsResult = new ScalacOptionsResult(resultItems); + return Either.forRight(scalacOptionsResult); + } + + public Either buildTargetScalaTestClasses( + ScalaTestClassesParams scalaTestClassesParams) { + LOGGER.info("buildTargetScalaTestClasses call with param: {}", scalaTestClassesParams); + + List resultItems = + targetsScalaTestClassesRulesResolver.getItemsForTargets( + scalaTestClassesParams.getTargets()); + + ScalaTestClassesResult scalaTestClassesResult = new ScalaTestClassesResult(resultItems); + + return Either.forRight(scalaTestClassesResult); + } + + public Either buildTargetScalaMainClasses( + ScalaMainClassesParams scalaMainClassesParams) { + LOGGER.info("buildTargetScalaMainClasses call with param: {}", scalaMainClassesParams); + + List resultItems = + targetsScalaMainClassesRulesResolver.getItemsForTargets( + scalaMainClassesParams.getTargets()); + + ScalaMainClassesResult result = + new ScalaMainClassesResult( + resultItems.stream() + .filter(item -> !item.getClasses().isEmpty()) + .collect(Collectors.toList())); + + return Either.forRight(result); + } + + private ScalaMainClassesItem mapRuleToMainClassesItem(Build.Rule rule) { + BuildTargetIdentifier targetId = new BuildTargetIdentifier(rule.getName()); + List mainClasses = collectMainClasses(rule); + return new ScalaMainClassesItem(targetId, mainClasses); + } + + private List collectMainClasses(Build.Rule rule) { + List targetOptions = + collectAttributesFromStringListValues(rule, Constants.JVM_FLAGS_ATTR_NAME); + List mainClassesNames = + collectAttributesFromStringValues(rule, Constants.MAIN_CLASS_ATTR_NAME); + List arguments = collectAttributesFromStringListValues(rule, Constants.ARGS_ATTR_NAME); + return mainClassesNames.stream() + .map(mainClassName -> new ScalaMainClass(mainClassName, arguments, targetOptions)) + .collect(Collectors.toList()); + } + + private List collectAttributesFromStringListValues(Build.Rule rule, String attrName) { + return getAttribute(rule, attrName) + .flatMap(attr -> attr.getStringListValueList().stream()) + .collect(Collectors.toList()); + } + + private List collectAttributesFromStringValues(Build.Rule rule, String attrName) { + return getAttribute(rule, attrName) + .map(Build.Attribute::getStringValue) + .collect(Collectors.toList()); + } + + private Stream getAttribute(Build.Rule rule, String name) { + return rule.getAttributeList().stream() + .filter(attr -> TargetsUtils.isAttributeSpecifiedAndHasGivenName(attr, name)); + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils/BUILD new file mode 100644 index 000000000..78d259ef2 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils/BUILD @@ -0,0 +1,13 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "utils", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "@io_bazel//src/main/protobuf:analysis_java_proto", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:com_google_guava_guava", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils/BuildManagerParsingUtils.java b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils/BuildManagerParsingUtils.java new file mode 100644 index 000000000..3917632d3 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/bsp/utils/BuildManagerParsingUtils.java @@ -0,0 +1,28 @@ +package org.jetbrains.bsp.bazel.server.bsp.utils; + +import ch.epfl.scala.bsp4j.BuildTargetTag; +import org.jetbrains.bsp.bazel.commons.Constants; + +public class BuildManagerParsingUtils { + + public static String convertOutputToPath(String output, String prefix) { + String pathToFile = output.replaceAll("(//|:)", "/"); + return prefix + pathToFile; + } + + public static String getRuleType(String ruleClass) { + if (ruleClass.contains(Constants.LIBRARY_RULE_TYPE)) { + return BuildTargetTag.LIBRARY; + } + + if (ruleClass.contains(Constants.BINARY_RULE_TYPE)) { + return BuildTargetTag.APPLICATION; + } + + if (ruleClass.contains(Constants.TEST_RULE_TYPE)) { + return BuildTargetTag.TEST; + } + + return BuildTargetTag.NO_IDE; + } +} diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/loggers/BUILD b/src/main/java/org/jetbrains/bsp/bazel/server/loggers/BUILD new file mode 100644 index 000000000..f34d54b58 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/loggers/BUILD @@ -0,0 +1,10 @@ +load("@rules_java//java:defs.bzl", "java_library") + +java_library( + name = "loggers", + srcs = glob(["*.java"]), + visibility = ["//visibility:public"], + deps = [ + "@maven//:ch_epfl_scala_bsp4j", + ], +) diff --git a/src/main/java/org/jetbrains/bsp/bazel/server/loggers/BuildClientLogger.java b/src/main/java/org/jetbrains/bsp/bazel/server/loggers/BuildClientLogger.java new file mode 100644 index 000000000..667c80a90 --- /dev/null +++ b/src/main/java/org/jetbrains/bsp/bazel/server/loggers/BuildClientLogger.java @@ -0,0 +1,33 @@ +package org.jetbrains.bsp.bazel.server.loggers; + +import ch.epfl.scala.bsp4j.BuildClient; +import ch.epfl.scala.bsp4j.LogMessageParams; +import ch.epfl.scala.bsp4j.MessageType; + +public class BuildClientLogger { + + private final BuildClient buildClient; + + public BuildClientLogger(BuildClient buildClient) { + this.buildClient = buildClient; + } + + public void logError(String errorMessage) { + logIfNotBlank(MessageType.ERROR, errorMessage); + } + + public void logMessage(String message) { + logIfNotBlank(MessageType.LOG, message); + } + + private void logIfNotBlank(MessageType messageType, String message) { + if (!message.trim().isEmpty()) { + log(messageType, message); + } + } + + private void log(MessageType messageType, String message) { + LogMessageParams params = new LogMessageParams(messageType, message.trim()); + buildClient.onBuildLogMessage(params); + } +} diff --git a/src/main/resources/BUILD b/src/main/resources/BUILD new file mode 100644 index 000000000..2e51e90ec --- /dev/null +++ b/src/main/resources/BUILD @@ -0,0 +1,5 @@ +filegroup( + name = "bsp-main-resources", + srcs = glob(["*.xml"]), + visibility = ["//visibility:public"], +) diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 000000000..fa8795c34 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/jetbrains/bsp/bazel/BUILD b/src/test/java/org/jetbrains/bsp/bazel/BUILD new file mode 100644 index 000000000..5bcae9e24 --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/BUILD @@ -0,0 +1,19 @@ +load("@rules_java//java:defs.bzl", "java_binary") + +java_binary( + name = "bsp-integration-test", + srcs = glob(["*.java"]), + main_class = "org.jetbrains.bsp.bazel.BazelBspServerTestRunner", + resources = ["//src/test/resources:bsp-test-resources"], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/commons", + "//src/main/java/org/jetbrains/bsp/bazel/server", + "@maven//:ch_epfl_scala_bsp4j", + "@maven//:ch_epfl_scala_bsp_testkit_2_13", + "@maven//:com_google_guava_guava", + "@maven//:io_vavr_vavr", + "@maven//:org_apache_logging_log4j_log4j_api", + "@maven//:org_apache_logging_log4j_log4j_core", + "@maven//:org_scala_lang_scala_library", + ], +) diff --git a/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerSingleTest.java b/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerSingleTest.java new file mode 100644 index 000000000..e4a7c7c18 --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerSingleTest.java @@ -0,0 +1,47 @@ +package org.jetbrains.bsp.bazel; + +import io.vavr.control.Try; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class BazelBspServerSingleTest { + + private static final Logger LOGGER = LogManager.getLogger(BazelBspServerSingleTest.class); + + private final String testName; + private final Runnable testToRun; + + private Optional> submittedTest; + + public BazelBspServerSingleTest(String testName, Runnable testToRun) { + this.testName = testName; + this.testToRun = testToRun; + this.submittedTest = Optional.empty(); + } + + public BazelBspServerSingleTest submit(ExecutorService executorService) { + submittedTest = Optional.of(executorService.submit(testToRun)); + + return this; + } + + public boolean executeTestWithTimeoutAndReturnTrueIfPassed(int timeoutInMinutes) { + return submittedTest + .map(test -> getSubmittedTestWithTimeout(test, timeoutInMinutes)) + .orElse(false); + } + + private boolean getSubmittedTestWithTimeout(Future submittedTest, int timeoutInMinutes) { + LOGGER.info("Running \"{}\" test...", testName); + + return Try.of(() -> submittedTest.get(timeoutInMinutes, TimeUnit.MINUTES)) + .onSuccess(e -> LOGGER.info("Test \"{}\" passed!", testName)) + .onFailure(e -> LOGGER.error("Test \"{}\" failed! Exception: {}", testName, e)) + .map(i -> true) + .getOrElse(false); + } +} diff --git a/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerTest.java b/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerTest.java new file mode 100644 index 000000000..2b085e76a --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerTest.java @@ -0,0 +1,266 @@ +package org.jetbrains.bsp.bazel; + +import ch.epfl.scala.bsp.testkit.client.TestClient; +import ch.epfl.scala.bsp.testkit.client.TestClient$; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +// TODO: these tests need some love again... +public class BazelBspServerTest { + + private static final Logger LOGGER = LogManager.getLogger(BazelBspServerTest.class); + + private static final Integer SUCCESS_EXIT_CODE = 0; + private static final Integer FAIL_EXIT_CODE = 1; + + private final ExecutorService executorService = Executors.newCachedThreadPool(); + + @SafeVarargs + private static Stream concat(Stream... streams) { + return Stream.of(streams).reduce(Stream::concat).orElseGet(Stream::empty); + } + + public void run() { + LOGGER.info("Creating TestClients..."); + + List testsToRun = + concat( + getSampleRepoTests().stream(), + getActionGraphV1Tests().stream(), + getActionGraphV2Tests().stream(), + getJava8ProjectTests().stream(), + getJava11ProjectTests().stream(), + getJavaDefaultProjectTests().stream(), + getEntireRepositoryImportTests().stream(), + getCppProjects().stream()) + .collect(Collectors.toList()); + + LOGGER.info("Created TestClients. Running BazelBspServerTest..."); + runTests(testsToRun); + } + + private List getCppProjects() { + TestClient client = + TestClient$.MODULE$.testInitialStructure( + BazelBspServerTestData.CPP_FULL_PATH, + ImmutableMap.of(), + BazelBspServerTestData.TEST_CLIENT_TIMEOUT_IN_MINUTES); + return ImmutableList.of( + new BazelBspServerSingleTest( + "cpp project", + () -> + client.testCompareWorkspaceTargetsResults( + BazelBspServerTestData.EXPECTED_BUILD_TARGETS_CPP)), + new BazelBspServerSingleTest( + "cpp options", + () -> + client.testCppOptions( + BazelBspServerTestData.CPP_OPTIONS_PARAMS, + BazelBspServerTestData.CPP_OPTIONS_RESULT))); + } + + private List getJava8ProjectTests() { + TestClient client = + TestClient$.MODULE$.testInitialStructure( + BazelBspServerTestData.JAVA_8_FULL_PATH, + ImmutableMap.of(), + BazelBspServerTestData.TEST_CLIENT_TIMEOUT_IN_MINUTES); + return ImmutableList.of( + new BazelBspServerSingleTest( + "java-8-project workspace build targets", + () -> + client.testCompareWorkspaceTargetsResults( + BazelBspServerTestData.EXPECTED_BUILD_TARGETS_JAVA_8))); + } + + private List getJava11ProjectTests() { + TestClient client = + TestClient$.MODULE$.testInitialStructure( + BazelBspServerTestData.JAVA_11_FULL_PATH, + ImmutableMap.of(), + BazelBspServerTestData.TEST_CLIENT_TIMEOUT_IN_MINUTES); + return ImmutableList.of( + new BazelBspServerSingleTest( + "java-11-project workspace build targets", + () -> + client.testCompareWorkspaceTargetsResults( + BazelBspServerTestData.EXPECTED_BUILD_TARGETS_JAVA_11))); + } + + private List getJavaDefaultProjectTests() { + TestClient client = + TestClient$.MODULE$.testInitialStructure( + BazelBspServerTestData.JAVA_11_FULL_PATH, + ImmutableMap.of(), + BazelBspServerTestData.TEST_CLIENT_TIMEOUT_IN_MINUTES); + return ImmutableList.of( + new BazelBspServerSingleTest( + "java-11-project workspace build targets", + () -> + client.testCompareWorkspaceTargetsResults( + BazelBspServerTestData.EXPECTED_BUILD_TARGETS_JAVA_11))); + } + + private List getEntireRepositoryImportTests() { + TestClient client = + TestClient$.MODULE$.testInitialStructure( + BazelBspServerTestData.REPO_PATH, + ImmutableMap.of(), + BazelBspServerTestData.TEST_CLIENT_TIMEOUT_IN_MINUTES); + return ImmutableList.of( + new BazelBspServerSingleTest( + "import entire repo", () -> client.testResolveProject(true, false))); + } + + private List getActionGraphV1Tests() { + TestClient client = + TestClient$.MODULE$.testInitialStructure( + BazelBspServerTestData.SAMPLE_REPO_FULL_PATH, + ImmutableMap.of(), + BazelBspServerTestData.TEST_CLIENT_TIMEOUT_IN_MINUTES); + + return ImmutableList.of( + new BazelBspServerSingleTest( + "actiong-graph-v1 javacopts test", + () -> + client.testJavacOptions( + BazelBspServerTestData.JAVAC_OPTIONS_PARAMS, + BazelBspServerTestData.EXPECTED_JAVAC_OPTIONS)), + new BazelBspServerSingleTest( + "actiong-graph-v1 scalacopts test", + () -> + client.testScalacOptions( + BazelBspServerTestData.SCALAC_OPTIONS_PARAMS, + BazelBspServerTestData.EXPECTED_SCALAC_OPTIONS))); + } + + private List getActionGraphV2Tests() { + TestClient client = + TestClient$.MODULE$.testInitialStructure( + BazelBspServerTestData.ACTION_GRAPH_V2_FULL_PATH, + ImmutableMap.of(), + BazelBspServerTestData.TEST_CLIENT_TIMEOUT_IN_MINUTES); + + return ImmutableList.of( + new BazelBspServerSingleTest( + "actiong-graph-v2 javacopts test", + () -> + client.testJavacOptions( + BazelBspServerTestData.JAVAC_OPTIONS_PARAMS_ACTION_GRAPH_V2, + BazelBspServerTestData.EXPECTED_JAVAC_OPTIONS_ACTION_GRAPH_V2)), + new BazelBspServerSingleTest( + "actiong-graph-v2 scalacopts test", + () -> + client.testScalacOptions( + BazelBspServerTestData.SCALAC_OPTIONS_PARAMS_ACTION_GRAPH_V2, + BazelBspServerTestData.EXPECTED_SCALAC_OPTIONS_ACTION_GRAPH_V2))); + } + + private List getSampleRepoTests() { + TestClient client = + TestClient$.MODULE$.testInitialStructure( + BazelBspServerTestData.SAMPLE_REPO_FULL_PATH, + ImmutableMap.of(), + BazelBspServerTestData.TEST_CLIENT_TIMEOUT_IN_MINUTES); + + return ImmutableList.of( + new BazelBspServerSingleTest( + "resolve project", () -> client.testResolveProject(false, false)), + new BazelBspServerSingleTest( + "compare workspace targets results", + () -> + client.testCompareWorkspaceTargetsResults( + BazelBspServerTestData.EXPECTED_BUILD_TARGETS)), + new BazelBspServerSingleTest( + "sources results", + () -> + client.testSourcesResults( + BazelBspServerTestData.EXPECTED_BUILD_TARGETS, + BazelBspServerTestData.EXPECTED_SOURCES)), + new BazelBspServerSingleTest( + "resources results", + () -> + client.testResourcesResults( + BazelBspServerTestData.EXPECTED_BUILD_TARGETS, + BazelBspServerTestData.EXPECTED_RESOURCES)), + new BazelBspServerSingleTest( + "inverse sources results", + () -> + client.testInverseSourcesResults( + BazelBspServerTestData.INVERSE_SOURCES_DOCUMENT, + BazelBspServerTestData.EXPECTED_INVERSE_SOURCES)), + new BazelBspServerSingleTest( + "dependency sources results", + () -> + client.testDependencySourcesResults( + BazelBspServerTestData.EXPECTED_BUILD_TARGETS, + BazelBspServerTestData.EXPECTED_DEPENDENCIES)), + new BazelBspServerSingleTest( + "Scala main classes", + () -> + client.testScalaMainClasses( + BazelBspServerTestData.SCALA_MAIN_CLASSES_PARAMS, + BazelBspServerTestData.EXPECTED_SCALA_MAIN_CLASSES)), + new BazelBspServerSingleTest( + "Scala test classes", + () -> + client.testScalaTestClasses( + BazelBspServerTestData.SCALA_TEST_CLASSES_PARAMS, + BazelBspServerTestData.EXPECTED_SCALA_TEST_CLASSES)) + // TODO one day we will uncomment them... + // new BazelBspServerSingleTest( + // "targets run unsuccessfully", + // client::testTargetsRunUnsuccessfully), + // new BazelBspServerSingleTest( + // "targets test unsuccessfully", + // client::testTargetsTestUnsuccessfully), + // new BazelBspServerSingleTest( + // "target capabilities", + // client::testTargetCapabilities) + ); + } + + private void runTests(List testsToRun) { + List submittedTests = submitTestsForExecution(testsToRun); + boolean didAllTestsPass = executeAllTestsAndReturnTrueIfAllPassed(submittedTests); + + exitProgramWithSuccessIfAllTestPassed(didAllTestsPass); + } + + private List submitTestsForExecution( + List testsToSubmit) { + LOGGER.info("Submitting tests for execution..."); + + return testsToSubmit.stream() + .map(test -> test.submit(executorService)) + .collect(Collectors.toList()); + } + + private boolean executeAllTestsAndReturnTrueIfAllPassed( + List submittedTests) { + LOGGER.info("Executing tests..."); + + return submittedTests.stream() + .allMatch( + test -> + test.executeTestWithTimeoutAndReturnTrueIfPassed( + BazelBspServerTestData.TEST_EXECUTION_TIMEOUT_IN_MINUTES)); + } + + private void exitProgramWithSuccessIfAllTestPassed(boolean didAllTestsPass) { + if (didAllTestsPass) { + LOGGER.info("All test passed - exiting with success"); + System.exit(SUCCESS_EXIT_CODE); + } + + LOGGER.fatal("Test failed - exiting with fail"); + System.exit(FAIL_EXIT_CODE); + } +} diff --git a/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerTestData.java b/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerTestData.java new file mode 100644 index 000000000..4e374ca2f --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerTestData.java @@ -0,0 +1,356 @@ +package org.jetbrains.bsp.bazel; + +import ch.epfl.scala.bsp4j.BuildTarget; +import ch.epfl.scala.bsp4j.BuildTargetCapabilities; +import ch.epfl.scala.bsp4j.BuildTargetDataKind; +import ch.epfl.scala.bsp4j.BuildTargetIdentifier; +import ch.epfl.scala.bsp4j.CppBuildTarget; +import ch.epfl.scala.bsp4j.CppOptionsItem; +import ch.epfl.scala.bsp4j.CppOptionsParams; +import ch.epfl.scala.bsp4j.CppOptionsResult; +import ch.epfl.scala.bsp4j.DependencySourcesItem; +import ch.epfl.scala.bsp4j.DependencySourcesResult; +import ch.epfl.scala.bsp4j.InverseSourcesResult; +import ch.epfl.scala.bsp4j.JavacOptionsItem; +import ch.epfl.scala.bsp4j.JavacOptionsParams; +import ch.epfl.scala.bsp4j.JavacOptionsResult; +import ch.epfl.scala.bsp4j.JvmBuildTarget; +import ch.epfl.scala.bsp4j.ResourcesItem; +import ch.epfl.scala.bsp4j.ResourcesResult; +import ch.epfl.scala.bsp4j.ScalaBuildTarget; +import ch.epfl.scala.bsp4j.ScalaMainClass; +import ch.epfl.scala.bsp4j.ScalaMainClassesItem; +import ch.epfl.scala.bsp4j.ScalaMainClassesParams; +import ch.epfl.scala.bsp4j.ScalaMainClassesResult; +import ch.epfl.scala.bsp4j.ScalaPlatform; +import ch.epfl.scala.bsp4j.ScalaTestClassesItem; +import ch.epfl.scala.bsp4j.ScalaTestClassesParams; +import ch.epfl.scala.bsp4j.ScalaTestClassesResult; +import ch.epfl.scala.bsp4j.ScalacOptionsItem; +import ch.epfl.scala.bsp4j.ScalacOptionsParams; +import ch.epfl.scala.bsp4j.ScalacOptionsResult; +import ch.epfl.scala.bsp4j.SourceItem; +import ch.epfl.scala.bsp4j.SourceItemKind; +import ch.epfl.scala.bsp4j.SourcesItem; +import ch.epfl.scala.bsp4j.SourcesResult; +import ch.epfl.scala.bsp4j.TextDocumentIdentifier; +import ch.epfl.scala.bsp4j.WorkspaceBuildTargetsResult; +import com.google.common.collect.ImmutableList; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import org.jetbrains.bsp.bazel.commons.Constants; + +class BazelBspServerTestData { + + private static final String BUILD_WORKSPACE_DIRECTORY = "BUILD_WORKSPACE_DIRECTORY"; + static final String REPO_PATH = System.getenv(BUILD_WORKSPACE_DIRECTORY); + + private static final String SAMPLE_REPO_PATH = "sample-repo"; + private static final String ACTION_GRAPH_V2 = "action-graph-v2"; + private static final String JAVA_8 = "java-8-project"; + private static final String JAVA_11 = "java-11-project"; + private static final String JAVA_DEFAULT = "java-default-project"; + private static final String CPP_PROJECT = "cpp-project"; + private static final String SAMPLE_REPO_EXAMPLE_PATH = SAMPLE_REPO_PATH + "/example"; + private static final String SAMPLE_REPO_DEP_PATH = SAMPLE_REPO_PATH + "/dep"; + + private static final String TEST_RESOURCES_PATH = "test-resources"; + private static final String WORKSPACE_DIR_PATH = REPO_PATH + "/" + TEST_RESOURCES_PATH; + + private static final BuildTargetIdentifier EXAMPLE_EXAMPLE_TARGET = + new BuildTargetIdentifier("//example:example"); + private static final BuildTargetIdentifier DEP_DEP_TARGET = + new BuildTargetIdentifier("//dep:dep"); + private static final BuildTargetIdentifier DEP_DEEPER_DEEPER_TARGET = + new BuildTargetIdentifier("//dep/deeper:deeper"); + private static final BuildTargetIdentifier EXAMPLE_EXAMPLE_TEST_TARGET = + new BuildTargetIdentifier("//example:example-test"); + private static final BuildTargetIdentifier WITHOUT_MAIN_CLASS_LIBRARY_TARGET = + new BuildTargetIdentifier("//target_without_main_class:library"); + private static final BuildTargetIdentifier WITHOUT_ARGS_BINARY_TARGET = + new BuildTargetIdentifier("//target_without_args:binary"); + private static final BuildTargetIdentifier WITHOUT_JVM_FLAGS_BINARY_TARGET = + new BuildTargetIdentifier("//target_without_jvm_flags:binary"); + private static final BuildTargetIdentifier DEP_JAVA_DEP_TARGET = + new BuildTargetIdentifier("//dep:java-dep"); + + static final Duration TEST_CLIENT_TIMEOUT_IN_MINUTES = Duration.ofMinutes(8); + static final Integer TEST_EXECUTION_TIMEOUT_IN_MINUTES = 30; + + static final String SAMPLE_REPO_FULL_PATH = WORKSPACE_DIR_PATH + "/" + SAMPLE_REPO_PATH; + static final String ACTION_GRAPH_V2_FULL_PATH = WORKSPACE_DIR_PATH + "/" + ACTION_GRAPH_V2; + static final String JAVA_8_FULL_PATH = WORKSPACE_DIR_PATH + "/" + JAVA_8; + static final String JAVA_11_FULL_PATH = WORKSPACE_DIR_PATH + "/" + JAVA_11; + static final String CPP_FULL_PATH = WORKSPACE_DIR_PATH + "/" + CPP_PROJECT; + static final String JAVA_DEFAULT_FULL_PATH = WORKSPACE_DIR_PATH + "/" + JAVA_DEFAULT; + static final String DEFAULT_JAVA_HOME = "external/local_jdk/"; + + private static final JvmBuildTarget EXAMPLE_JVM_TARGET_JAVA_8 = + new JvmBuildTarget(DEFAULT_JAVA_HOME, "8"); + private static final JvmBuildTarget EXAMPLE_JVM_TARGET_JAVA_8_SCALA_TARGET = + new JvmBuildTarget(null, "8"); + private static final JvmBuildTarget EXAMPLE_JVM_TARGET_JAVA_11 = + new JvmBuildTarget(DEFAULT_JAVA_HOME, "11"); + private static final CppBuildTarget EXAMPLE_CPP_TARGET = + new CppBuildTarget(null, "compiler", "/bin/gcc", "/bin/gcc"); + + private static final List SCALA_TARGET_JARS = + ImmutableList.of( + "__main__/external/io_bazel_rules_scala_scala_compiler/scala-compiler-2.12.8.jar", + "__main__/external/io_bazel_rules_scala_scala_library/scala-library-2.12.8.jar", + "__main__/external/io_bazel_rules_scala_scala_reflect/scala-reflect-2.12.8.jar"); + + private static final ScalaBuildTarget EXAMPLE_SCALA_TARGET = + new ScalaBuildTarget( + "org.scala-lang", "2.12.8", "2.12", ScalaPlatform.JVM, SCALA_TARGET_JARS) { + { + setJvmBuildTarget(EXAMPLE_JVM_TARGET_JAVA_8_SCALA_TARGET); + } + }; + + static final WorkspaceBuildTargetsResult EXPECTED_BUILD_TARGETS = + new WorkspaceBuildTargetsResult( + ImmutableList.of( + new BuildTarget( + EXAMPLE_EXAMPLE_TARGET, + ImmutableList.of(), + ImmutableList.of(Constants.SCALA), + ImmutableList.of(DEP_DEP_TARGET), + new BuildTargetCapabilities(true, false, true)), + new BuildTarget( + DEP_DEP_TARGET, + ImmutableList.of(), + ImmutableList.of(Constants.JAVA, Constants.SCALA), + ImmutableList.of(DEP_DEEPER_DEEPER_TARGET), + new BuildTargetCapabilities(true, false, false)))) { + { + getTargets() + .forEach( + target -> { + target.setData(EXAMPLE_SCALA_TARGET); + target.setDataKind(BuildTargetDataKind.SCALA); + }); + } + }; + + static final List DEPENDENCIES = + ImmutableList.of( + "https/repo1.maven.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3-sources.jar", + "https/repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar", + "https/repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1-sources.jar", + "https/repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations/2.3.2/error_prone_annotations-2.3.2-sources.jar", + "https/repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations/1.17/animal-sniffer-annotations-1.17-sources.jar", + "https/repo1.maven.org/maven2/org/checkerframework/checker-qual/2.8.1/checker-qual-2.8.1-sources.jar", + "https/repo1.maven.org/maven2/com/google/guava/guava/28.0-jre/guava-28.0-jre-sources.jar"); + + static final DependencySourcesResult EXPECTED_DEPENDENCIES = + new DependencySourcesResult( + ImmutableList.of( + new DependencySourcesItem(EXAMPLE_EXAMPLE_TARGET, DEPENDENCIES), + new DependencySourcesItem(DEP_DEP_TARGET, DEPENDENCIES))); + + static final SourcesResult EXPECTED_SOURCES = + new SourcesResult( + ImmutableList.of( + new SourcesItem( + EXAMPLE_EXAMPLE_TARGET, + ImmutableList.of( + new SourceItem( + SAMPLE_REPO_EXAMPLE_PATH + "/Example.scala", + SourceItemKind.FILE, + false))), + new SourcesItem( + DEP_DEP_TARGET, + ImmutableList.of( + new SourceItem( + SAMPLE_REPO_DEP_PATH + "/Test.scala", SourceItemKind.FILE, false), + new SourceItem( + SAMPLE_REPO_DEP_PATH + "/JavaTest.java", SourceItemKind.FILE, false), + new SourceItem( + SAMPLE_REPO_DEP_PATH + "/Dep.scala", SourceItemKind.FILE, false))))); + + static final ResourcesResult EXPECTED_RESOURCES = + new ResourcesResult( + ImmutableList.of( + new ResourcesItem( + EXAMPLE_EXAMPLE_TARGET, + ImmutableList.of( + SAMPLE_REPO_EXAMPLE_PATH + "/file.txt", + SAMPLE_REPO_EXAMPLE_PATH + "/file2.txt")))); + + static final TextDocumentIdentifier INVERSE_SOURCES_DOCUMENT = + new TextDocumentIdentifier("file://" + SAMPLE_REPO_FULL_PATH + "/dep/Dep.scala"); + + static final InverseSourcesResult EXPECTED_INVERSE_SOURCES = + new InverseSourcesResult(ImmutableList.of(DEP_DEP_TARGET)); + + static final ScalaMainClassesParams SCALA_MAIN_CLASSES_PARAMS = + new ScalaMainClassesParams( + ImmutableList.of( + EXAMPLE_EXAMPLE_TARGET, + WITHOUT_MAIN_CLASS_LIBRARY_TARGET, + WITHOUT_ARGS_BINARY_TARGET, + WITHOUT_JVM_FLAGS_BINARY_TARGET)); + + static final ScalaMainClassesResult EXPECTED_SCALA_MAIN_CLASSES = + new ScalaMainClassesResult( + ImmutableList.of( + new ScalaMainClassesItem( + EXAMPLE_EXAMPLE_TARGET, + Collections.singletonList( + new ScalaMainClass( + "example.Example", + ImmutableList.of("arg1", "arg2"), + ImmutableList.of("-Xms2G -Xmx5G")))), + new ScalaMainClassesItem( + WITHOUT_ARGS_BINARY_TARGET, + Collections.singletonList( + new ScalaMainClass( + "example.Example", + ImmutableList.of(), + ImmutableList.of("-Xms2G -Xmx5G")))), + new ScalaMainClassesItem( + WITHOUT_JVM_FLAGS_BINARY_TARGET, + Collections.singletonList( + new ScalaMainClass( + "example.Example", + ImmutableList.of("arg1", "arg2"), + ImmutableList.of()))))); + + static final ScalaTestClassesParams SCALA_TEST_CLASSES_PARAMS = + new ScalaTestClassesParams( + ImmutableList.of(EXAMPLE_EXAMPLE_TARGET, EXAMPLE_EXAMPLE_TEST_TARGET)); + + static final ScalaTestClassesResult EXPECTED_SCALA_TEST_CLASSES = + new ScalaTestClassesResult( + ImmutableList.of( + new ScalaTestClassesItem( + EXAMPLE_EXAMPLE_TEST_TARGET, ImmutableList.of("example.ExampleTest")))); + + static final JavacOptionsParams JAVAC_OPTIONS_PARAMS = + new JavacOptionsParams(ImmutableList.of(EXAMPLE_EXAMPLE_TARGET, DEP_JAVA_DEP_TARGET)); + + static final JavacOptionsResult EXPECTED_JAVAC_OPTIONS = + new JavacOptionsResult( + ImmutableList.of( + new JavacOptionsItem( + EXAMPLE_EXAMPLE_TARGET, ImmutableList.of(), ImmutableList.of(), ""), + new JavacOptionsItem( + DEP_JAVA_DEP_TARGET, + ImmutableList.of("-Werror", "-Xlint:all"), + ImmutableList.of(), + ""))); + + static final ScalacOptionsParams SCALAC_OPTIONS_PARAMS = + new ScalacOptionsParams(ImmutableList.of(EXAMPLE_EXAMPLE_TARGET, DEP_DEP_TARGET)); + + static final ScalacOptionsResult EXPECTED_SCALAC_OPTIONS = + new ScalacOptionsResult( + ImmutableList.of( + new ScalacOptionsItem( + EXAMPLE_EXAMPLE_TARGET, + ImmutableList.of("-target:jvm-1.8"), + ImmutableList.of( + "__main__/external/io_bazel_rules_scala_scala_library/scala-library-2.12.8.jar", + "__main__/external/io_bazel_rules_scala_scala_reflect/scala-reflect-2.12.8.jar", + "/bin/external/io_bazel_rules_scala/src/java/io/bazel/rulesscala/scalac/scalac.jar", + "/bin/dep/deeper/deeper.jar", + "/bin/dep/dep.jar"), + "bin/example/"), + new ScalacOptionsItem( + DEP_DEP_TARGET, + ImmutableList.of(), + ImmutableList.of( + "__main__/external/io_bazel_rules_scala_scala_library/scala-library-2.12.8.jar", + "__main__/external/io_bazel_rules_scala_scala_reflect/scala-reflect-2.12.8.jar", + "bin/external/io_bazel_rules_scala/src/java/io/bazel/rulesscala/scalac/scalac.jar", + "bin/dep/deeper/deeper.jar"), + "bin/dep/"))); + + static final JavacOptionsParams JAVAC_OPTIONS_PARAMS_ACTION_GRAPH_V2 = + new JavacOptionsParams(ImmutableList.of(EXAMPLE_EXAMPLE_TARGET, DEP_JAVA_DEP_TARGET)); + + static final JavacOptionsResult EXPECTED_JAVAC_OPTIONS_ACTION_GRAPH_V2 = + new JavacOptionsResult( + ImmutableList.of( + new JavacOptionsItem( + EXAMPLE_EXAMPLE_TARGET, ImmutableList.of(), ImmutableList.of(), ""))); + + static final ScalacOptionsParams SCALAC_OPTIONS_PARAMS_ACTION_GRAPH_V2 = + new ScalacOptionsParams(ImmutableList.of(EXAMPLE_EXAMPLE_TARGET, DEP_DEP_TARGET)); + + static final ScalacOptionsResult EXPECTED_SCALAC_OPTIONS_ACTION_GRAPH_V2 = + new ScalacOptionsResult( + ImmutableList.of( + new ScalacOptionsItem( + EXAMPLE_EXAMPLE_TARGET, + ImmutableList.of("-target:jvm-1.8"), + ImmutableList.of( + "__main__/external/io_bazel_rules_scala_scala_library/scala-library-2.12.8.jar", + "__main__/external/io_bazel_rules_scala_scala_reflect/scala-reflect-2.12.8.jar", + "/bin/external/io_bazel_rules_scala/src/java/io/bazel/rulesscala/scalac/scalac.jar"), + "bin/example/"))); + + static final BuildTarget EXAMPLE_JAVA_TARGET_JAVA_8 = + new BuildTarget( + EXAMPLE_EXAMPLE_TARGET, + ImmutableList.of(), + ImmutableList.of(Constants.JAVA), + ImmutableList.of(), + new BuildTargetCapabilities(true, false, true)) { + { + setData(EXAMPLE_JVM_TARGET_JAVA_8); + setDataKind(BuildTargetDataKind.JVM); + } + }; + static final WorkspaceBuildTargetsResult EXPECTED_BUILD_TARGETS_JAVA_8 = + new WorkspaceBuildTargetsResult(ImmutableList.of(EXAMPLE_JAVA_TARGET_JAVA_8)); + + static final BuildTarget EXAMPLE_JAVA_TARGET_JAVA_11 = + new BuildTarget( + EXAMPLE_EXAMPLE_TARGET, + ImmutableList.of(), + ImmutableList.of(Constants.JAVA), + ImmutableList.of(), + new BuildTargetCapabilities(true, false, true)) { + { + setData(EXAMPLE_JVM_TARGET_JAVA_11); + setDataKind(BuildTargetDataKind.JVM); + } + }; + static final WorkspaceBuildTargetsResult EXPECTED_BUILD_TARGETS_JAVA_11 = + new WorkspaceBuildTargetsResult(ImmutableList.of(EXAMPLE_JAVA_TARGET_JAVA_11)); + + static final BuildTargetIdentifier GOOGLE_TEST_IDENTIFIER = + new BuildTargetIdentifier("@com_google_googletest//:gtest_main"); + + static final BuildTarget EXAMPLE_CPP_BUILD_TARGET = + new BuildTarget( + EXAMPLE_EXAMPLE_TARGET, + ImmutableList.of(), + ImmutableList.of(Constants.CPP), + ImmutableList.of(GOOGLE_TEST_IDENTIFIER), + new BuildTargetCapabilities(true, false, true)) { + { + setData(EXAMPLE_CPP_TARGET); + setDataKind(BuildTargetDataKind.CPP); + } + }; + + static final WorkspaceBuildTargetsResult EXPECTED_BUILD_TARGETS_CPP = + new WorkspaceBuildTargetsResult(ImmutableList.of(EXAMPLE_CPP_BUILD_TARGET)); + + static final CppOptionsParams CPP_OPTIONS_PARAMS = + new CppOptionsParams(ImmutableList.of(EXAMPLE_EXAMPLE_TARGET)); + + static final CppOptionsItem CPP_OPTIONS_ITEM = + new CppOptionsItem( + EXAMPLE_EXAMPLE_TARGET, + ImmutableList.of("-Iexternal/gtest/include"), + ImmutableList.of("BOOST_FALLTHROUGH"), + ImmutableList.of("-pthread")); + + static final CppOptionsResult CPP_OPTIONS_RESULT = + new CppOptionsResult(ImmutableList.of(CPP_OPTIONS_ITEM)); +} diff --git a/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerTestRunner.java b/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerTestRunner.java new file mode 100644 index 000000000..0f1daf3e8 --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/BazelBspServerTestRunner.java @@ -0,0 +1,16 @@ +package org.jetbrains.bsp.bazel; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class BazelBspServerTestRunner { + + private static final Logger LOGGER = LogManager.getLogger(BazelBspServerTestRunner.class); + + public static void main(String[] args) { + LOGGER.info("Starting BazelBspServerTest..."); + + BazelBspServerTest bazelBspServerTest = new BazelBspServerTest(); + bazelBspServerTest.run(); + } +} diff --git a/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/BUILD b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/BUILD new file mode 100644 index 000000000..5dfbafe41 --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/BUILD @@ -0,0 +1,20 @@ +load("@rules_java//java:defs.bzl", "java_test") + +java_test( + name = "ProjectViewParserImplTest", + size = "small", + srcs = [ + "ProjectViewParserImplTest.java", + "ProjectViewParserMockTestImpl.java", + ], + resources = ["//src/test/resources/projectview:projectview-resources"], + runtime_deps = [ + "@maven//:junit_junit", + ], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser", + ], +) diff --git a/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParserImplTest.java b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParserImplTest.java new file mode 100644 index 000000000..d7e8356f0 --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParserImplTest.java @@ -0,0 +1,76 @@ +package org.jetbrains.bsp.bazel.projectview.parser; + +import static org.junit.Assert.assertEquals; + +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.DirectoriesSection; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.TargetsSection; +import org.junit.Before; +import org.junit.Test; + +public class ProjectViewParserImplTest { + + private static final DirectoriesSection EXPECTED_DIRECTORIES_SECTION = + new DirectoriesSection( + ImmutableList.of(Paths.get(".")), + ImmutableList.of( + Paths.get("excluded_dir1"), Paths.get("excluded_dir2"), Paths.get("excluded_dir3"))); + + private static final TargetsSection EXPECTED_TARGETS_SECTION = + new TargetsSection( + ImmutableList.of("//included_target1:test1", "//included_target1:test2"), + ImmutableList.of("//excluded_target1:test1")); + + private static final ProjectView EXPECTED_PROJECT_VIEW = + ProjectView.builder() + .directories(EXPECTED_DIRECTORIES_SECTION) + .targets(EXPECTED_TARGETS_SECTION) + .build(); + + private ProjectViewParser parser; + + @Before + public void before() { + this.parser = new ProjectViewParserMockTestImpl(); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowMissingDirectoriesSection() throws IOException { + Path projectViewFilePath = Paths.get("/projectview/projectViewWithoutDirectories.bazelproject"); + parser.parse(projectViewFilePath); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowMissingTargetsSection() throws IOException { + Path projectViewFilePath = Paths.get("/projectview/projectViewWithoutTargets.bazelproject"); + parser.parse(projectViewFilePath); + } + + @Test + public void shouldParseFile() throws IOException { + Path projectViewFilePath = Paths.get("/projectview/projectView.bazelproject"); + ProjectView projectView = parser.parse(projectViewFilePath); + + assertEquals(EXPECTED_PROJECT_VIEW, projectView); + } + + @Test + public void shouldParseFileWithTabs() throws IOException { + Path projectViewFilePath = Paths.get("/projectview/projectViewWithTabs.bazelproject"); + ProjectView projectView = parser.parse(projectViewFilePath); + + assertEquals(EXPECTED_PROJECT_VIEW, projectView); + } + + @Test + public void shouldParseFileWithImport() throws IOException { + Path projectViewFilePath = Paths.get("/projectview/projectViewWithImport.bazelproject"); + ProjectView projectView = parser.parse(projectViewFilePath); + + assertEquals(EXPECTED_PROJECT_VIEW, projectView); + } +} diff --git a/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParserMockTestImpl.java b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParserMockTestImpl.java new file mode 100644 index 000000000..5d3134c5b --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/ProjectViewParserMockTestImpl.java @@ -0,0 +1,22 @@ +package org.jetbrains.bsp.bazel.projectview.parser; + +import com.google.common.base.Charsets; +import com.google.common.io.CharStreams; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Path; +import org.jetbrains.bsp.bazel.projectview.model.ProjectView; + +public class ProjectViewParserMockTestImpl extends ProjectViewParserImpl { + + @Override + public ProjectView parse(Path projectViewPath) throws IOException { + InputStream inputStream = + ProjectViewParserMockTestImpl.class.getResourceAsStream(projectViewPath.toString()); + // we read file content instead of passing plain file due to bazel resources packaging + String fileContent = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8)); + + return parse(fileContent); + } +} diff --git a/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/BUILD b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/BUILD new file mode 100644 index 000000000..eea806991 --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/BUILD @@ -0,0 +1,33 @@ +load("@rules_java//java:defs.bzl", "java_test") + +java_test( + name = "DirectoriesSectionParserTest", + size = "small", + srcs = ["DirectoriesSectionParserTest.java"], + resources = ["//src/test/resources:bsp-test-resources"], + runtime_deps = [ + "@maven//:junit_junit", + ], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific", + ], +) + +java_test( + name = "TargetsSectionParserTest", + size = "small", + srcs = ["TargetsSectionParserTest.java"], + resources = ["//src/test/resources:bsp-test-resources"], + runtime_deps = [ + "@maven//:junit_junit", + ], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/model/sections/specific", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections", + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific", + ], +) diff --git a/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/DirectoriesSectionParserTest.java b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/DirectoriesSectionParserTest.java new file mode 100644 index 000000000..2acd84f6e --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/DirectoriesSectionParserTest.java @@ -0,0 +1,83 @@ +package org.jetbrains.bsp.bazel.projectview.parser.sections.specific; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.DirectoriesSection; +import org.jetbrains.bsp.bazel.projectview.parser.sections.ProjectViewSectionParser; +import org.junit.Before; +import org.junit.Test; + +public class DirectoriesSectionParserTest { + + private ProjectViewSectionParser parser; + + @Before + public void before() { + this.parser = new DirectoriesSectionParser(); + } + + @Test + public void shouldRecognizeSectionHeader() { + String directoriesSectionHeader = "directories"; + + assertTrue(parser.isSectionParsable(directoriesSectionHeader)); + } + + @Test + public void shouldNotRecognizeInvalidSectionHeader() { + String directoriesSectionHeader = "invalid_header"; + + assertFalse(parser.isSectionParsable(directoriesSectionHeader)); + } + + @Test + public void shouldParseIncludedDirectories() { + String entryBody = "test_included1 test_included2 test_included3 \n"; + + DirectoriesSection section = parser.parse(entryBody); + + List expectedIncludedPaths = + ImmutableList.of( + Paths.get("test_included1"), Paths.get("test_included2"), Paths.get("test_included3")); + List expectedExcludedPaths = ImmutableList.of(); + + assertEquals(expectedIncludedPaths, section.getIncludedDirectories()); + assertEquals(expectedExcludedPaths, section.getExcludedDirectories()); + } + + @Test + public void shouldParseExcludedDirectories() { + String entryBody = "-test_excluded1 -test_excluded2 -test_excluded3 \n"; + + DirectoriesSection section = parser.parse(entryBody); + + List expectedIncludedPaths = ImmutableList.of(); + List expectedExcludedPaths = + ImmutableList.of( + Paths.get("test_excluded1"), Paths.get("test_excluded2"), Paths.get("test_excluded3")); + + assertEquals(expectedIncludedPaths, section.getIncludedDirectories()); + assertEquals(expectedExcludedPaths, section.getExcludedDirectories()); + } + + @Test + public void shouldParseIncludedAndExcludedDirectories() { + String entryBody = "test_included1 -test_excluded1 test_included2 \n-test_excluded2\n"; + + DirectoriesSection section = parser.parse(entryBody); + + List expectedIncludedPaths = + ImmutableList.of(Paths.get("test_included1"), Paths.get("test_included2")); + List expectedExcludedPaths = + ImmutableList.of(Paths.get("test_excluded1"), Paths.get("test_excluded2")); + + assertEquals(expectedIncludedPaths, section.getIncludedDirectories()); + assertEquals(expectedExcludedPaths, section.getExcludedDirectories()); + } +} diff --git a/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/TargetsSectionParserTest.java b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/TargetsSectionParserTest.java new file mode 100644 index 000000000..0349cb7c4 --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/sections/specific/TargetsSectionParserTest.java @@ -0,0 +1,83 @@ +package org.jetbrains.bsp.bazel.projectview.parser.sections.specific; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.jetbrains.bsp.bazel.projectview.model.sections.specific.TargetsSection; +import org.jetbrains.bsp.bazel.projectview.parser.sections.ProjectViewSectionParser; +import org.junit.Before; +import org.junit.Test; + +public class TargetsSectionParserTest { + + private ProjectViewSectionParser parser; + + @Before + public void before() { + this.parser = new TargetsSectionParser(); + } + + @Test + public void shouldRecognizeSectionHeader() { + String directoriesSectionHeader = "targets"; + + assertTrue(parser.isSectionParsable(directoriesSectionHeader)); + } + + @Test + public void shouldNotRecognizeInvalidSectionHeader() { + String directoriesSectionHeader = "invalid_header"; + + assertFalse(parser.isSectionParsable(directoriesSectionHeader)); + } + + @Test + public void shouldParseIncludedTargets() { + String entryBody = + " //test_included1:test1\n //:test_included1:test2\n //:test_included2:test1\n\n"; + + TargetsSection section = parser.parse(entryBody); + + List expectedIncludedTargets = + ImmutableList.of( + "//test_included1:test1", "//:test_included1:test2", "//:test_included2:test1"); + List expectedExcludedTargets = ImmutableList.of(); + + assertEquals(expectedIncludedTargets, section.getIncludedTargets()); + assertEquals(expectedExcludedTargets, section.getExcludedTargets()); + } + + @Test + public void shouldParseExcludedTargets() { + String entryBody = + " -//test_excluded1:test1\n -//test_excluded1:test2\n -//test_excluded2:test1\n\n"; + + TargetsSection section = parser.parse(entryBody); + + List expectedIncludedTargets = ImmutableList.of(); + List expectedExcludedTargets = + ImmutableList.of( + "//test_excluded1:test1", "//test_excluded1:test2", "//test_excluded2:test1"); + + assertEquals(expectedIncludedTargets, section.getIncludedTargets()); + assertEquals(expectedExcludedTargets, section.getExcludedTargets()); + } + + @Test + public void shouldParseIncludedAndExcludedTargets() { + String entryBody = + " -//test_excluded1:test1\n //test_included1:test1\n -//test_excluded1:test2\n\n"; + + TargetsSection section = parser.parse(entryBody); + + List expectedIncludedTargets = ImmutableList.of("//test_included1:test1"); + List expectedExcludedTargets = + ImmutableList.of("//test_excluded1:test1", "//test_excluded1:test2"); + + assertEquals(expectedIncludedTargets, section.getIncludedTargets()); + assertEquals(expectedExcludedTargets, section.getExcludedTargets()); + } +} diff --git a/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/BUILD b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/BUILD new file mode 100644 index 000000000..2f8ba32af --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/BUILD @@ -0,0 +1,14 @@ +load("@rules_java//java:defs.bzl", "java_test") + +java_test( + name = "ProjectViewSectionSplitterTest", + size = "small", + srcs = ["ProjectViewSectionSplitterTest.java"], + resources = ["//src/test/resources:bsp-test-resources"], + runtime_deps = [ + "@maven//:junit_junit", + ], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/projectview/parser/splitter", + ], +) diff --git a/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewSectionSplitterTest.java b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewSectionSplitterTest.java new file mode 100644 index 000000000..1e4432762 --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/projectview/parser/splitter/ProjectViewSectionSplitterTest.java @@ -0,0 +1,67 @@ +package org.jetbrains.bsp.bazel.projectview.parser.splitter; + +import static org.junit.Assert.assertEquals; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; + +public class ProjectViewSectionSplitterTest { + + @Test + public void shouldParseEmptyFile() { + String emptyContent = ""; + + List result = ProjectViewSectionSplitter.split(emptyContent); + List expectedResult = ImmutableList.of(); + + assertEquals(expectedResult, result); + } + + @Test + public void shouldParseRegularFile() { + String fileContent = + "import path/to/file.bazelproject" + + "\n" + + "directories: " + + ". " + + "-excluded_dir1 " + + "-excluded_dir2 " + + "-excluded_dir3" + + "\n" + + "targets:\n" + + " //included_target1:test1\n" + + " -//excluded_target1:test1\n" + + " //included_target1:test2\n" + + "\n" + + "workspace_type: not_parsed\n" + + "\n" + + "build_flags:\n" + + " --not_parsed_flag\n" + + "\n" + + "test_sources:\n" + + " *test/not/parsed1/*\n" + + " *test/not/parsed2/*\n" + + "\n"; + + List result = ProjectViewSectionSplitter.split(fileContent); + List expectedResult = + ImmutableList.of( + new ProjectViewRawSection("import", " path/to/file.bazelproject\n"), + new ProjectViewRawSection( + "directories", " . -excluded_dir1 -excluded_dir2 -excluded_dir3\n"), + new ProjectViewRawSection( + "targets", + "\n" + + " //included_target1:test1\n" + + " -//excluded_target1:test1\n" + + " //included_target1:test2\n" + + "\n"), + new ProjectViewRawSection("workspace_type", " not_parsed\n\n"), + new ProjectViewRawSection("build_flags", "\n --not_parsed_flag\n\n"), + new ProjectViewRawSection( + "test_sources", "\n *test/not/parsed1/*\n *test/not/parsed2/*\n\n")); + + assertEquals(expectedResult, result); + } +} diff --git a/src/test/java/org/jetbrains/bsp/bazel/server/bazel/data/BUILD b/src/test/java/org/jetbrains/bsp/bazel/server/bazel/data/BUILD new file mode 100644 index 000000000..c24a84c39 --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/server/bazel/data/BUILD @@ -0,0 +1,14 @@ +load("@rules_java//java:defs.bzl", "java_test") + +java_test( + name = "SemanticVersionTest", + size = "small", + srcs = ["SemanticVersionTest.java"], + resources = ["//src/test/resources:bsp-test-resources"], + runtime_deps = [ + "@maven//:junit_junit", + ], + deps = [ + "//src/main/java/org/jetbrains/bsp/bazel/server/bazel/data", + ], +) diff --git a/src/test/java/org/jetbrains/bsp/bazel/server/bazel/data/SemanticVersionTest.java b/src/test/java/org/jetbrains/bsp/bazel/server/bazel/data/SemanticVersionTest.java new file mode 100644 index 000000000..88efe236a --- /dev/null +++ b/src/test/java/org/jetbrains/bsp/bazel/server/bazel/data/SemanticVersionTest.java @@ -0,0 +1,41 @@ +package org.jetbrains.bsp.bazel.server.bazel.data; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class SemanticVersionTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowForNull() { + new SemanticVersion(null); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowForTooShortVersion() { + new SemanticVersion("1.2"); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowForTooLongVersion() { + new SemanticVersion("1.2.3.4.5"); + } + + @Test + public void shouldParseSimpleVersion() { + SemanticVersion version = new SemanticVersion("1.2.3"); + + assertEquals(1, version.getMajorVersion()); + assertEquals(2, version.getMinorVersion()); + assertEquals(3, version.getPatchVersion()); + } + + @Test + public void shouldParseHomebrewVersion() { + SemanticVersion version = new SemanticVersion("1.2.3-homebrew"); + + assertEquals(1, version.getMajorVersion()); + assertEquals(2, version.getMinorVersion()); + assertEquals(3, version.getPatchVersion()); + } +} diff --git a/src/test/resources/BUILD b/src/test/resources/BUILD new file mode 100644 index 000000000..28426fe6a --- /dev/null +++ b/src/test/resources/BUILD @@ -0,0 +1,5 @@ +filegroup( + name = "bsp-test-resources", + srcs = glob(["*.xml"]), + visibility = ["//visibility:public"], +) diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 000000000..9f08521ae --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/projectview/BUILD b/src/test/resources/projectview/BUILD new file mode 100644 index 000000000..09681adaa --- /dev/null +++ b/src/test/resources/projectview/BUILD @@ -0,0 +1,5 @@ +filegroup( + name = "projectview-resources", + srcs = glob(["*.bazelproject"]), + visibility = ["//visibility:public"], +) diff --git a/src/test/resources/projectview/projectView.bazelproject b/src/test/resources/projectview/projectView.bazelproject new file mode 100644 index 000000000..293c04670 --- /dev/null +++ b/src/test/resources/projectview/projectView.bazelproject @@ -0,0 +1,15 @@ +directories: . -excluded_dir1 -excluded_dir2 -excluded_dir3 + +workspace_type: not_parsed + +targets: + //included_target1:test1 + -//excluded_target1:test1 + //included_target1:test2 + +build_flags: + --not_parsed_flag + +test_sources: + *test/not/parsed1/* + *test/not/parsed2/* diff --git a/src/test/resources/projectview/projectViewToImport.bazelproject b/src/test/resources/projectview/projectViewToImport.bazelproject new file mode 100644 index 000000000..2cf34afbc --- /dev/null +++ b/src/test/resources/projectview/projectViewToImport.bazelproject @@ -0,0 +1,12 @@ +directories: . -excluded_dir1 + +workspace_type: not_parsed1 + +targets: + //included_target1:test1 + +build_flags: + --not_parsed_flag1 + +test_sources: + *test/not/parsed1/* diff --git a/src/test/resources/projectview/projectViewWithImport.bazelproject b/src/test/resources/projectview/projectViewWithImport.bazelproject new file mode 100644 index 000000000..a17c1a4eb --- /dev/null +++ b/src/test/resources/projectview/projectViewWithImport.bazelproject @@ -0,0 +1,7 @@ +import /projectview/projectViewToImport.bazelproject + +directories: -excluded_dir2 -excluded_dir3 + +targets: + -//excluded_target1:test1 + //included_target1:test2 diff --git a/src/test/resources/projectview/projectViewWithTabs.bazelproject b/src/test/resources/projectview/projectViewWithTabs.bazelproject new file mode 100644 index 000000000..53142c786 --- /dev/null +++ b/src/test/resources/projectview/projectViewWithTabs.bazelproject @@ -0,0 +1,15 @@ +directories: . -excluded_dir1 -excluded_dir2 -excluded_dir3 + +workspace_type: not_parsed + +targets: + //included_target1:test1 + -//excluded_target1:test1 + //included_target1:test2 + +build_flags: + --not_parsed_flag + +test_sources: + *test/not/parsed1/* + *test/not/parsed2/* diff --git a/src/test/resources/projectview/projectViewWithoutDirectories.bazelproject b/src/test/resources/projectview/projectViewWithoutDirectories.bazelproject new file mode 100644 index 000000000..74cd646d0 --- /dev/null +++ b/src/test/resources/projectview/projectViewWithoutDirectories.bazelproject @@ -0,0 +1,13 @@ +workspace_type: not_parsed + +targets: + //included_target1:test1 + -//excluded_target1:test1 + //included_target1:test2 + +build_flags: + --not_parsed_flag + +test_sources: + *test/not/parsed1/* + *test/not/parsed2/* diff --git a/src/test/resources/projectview/projectViewWithoutTargets.bazelproject b/src/test/resources/projectview/projectViewWithoutTargets.bazelproject new file mode 100644 index 000000000..3f6beb3f5 --- /dev/null +++ b/src/test/resources/projectview/projectViewWithoutTargets.bazelproject @@ -0,0 +1,10 @@ +directories: . -excluded_dir1 -excluded_dir2 -excluded_dir3 + +workspace_type: not_parsed + +build_flags: + --not_parsed_flag + +test_sources: + *test/not/parsed1/* + *test/not/parsed2/* diff --git a/test-resources/action-graph-v2/.bazelversion b/test-resources/action-graph-v2/.bazelversion new file mode 100644 index 000000000..fcdb2e109 --- /dev/null +++ b/test-resources/action-graph-v2/.bazelversion @@ -0,0 +1 @@ +4.0.0 diff --git a/test-resources/action-graph-v2/.gitignore b/test-resources/action-graph-v2/.gitignore new file mode 100644 index 000000000..18481deff --- /dev/null +++ b/test-resources/action-graph-v2/.gitignore @@ -0,0 +1,5 @@ +/.bazelbsp/ +/.bsp/ +/.idea/ +/.ijwb/ +/bazel-* \ No newline at end of file diff --git a/test-resources/action-graph-v2/WORKSPACE b/test-resources/action-graph-v2/WORKSPACE new file mode 100644 index 000000000..926efa2d3 --- /dev/null +++ b/test-resources/action-graph-v2/WORKSPACE @@ -0,0 +1,66 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# bazel-skylib 0.8.0 released 2019.03.20 (https://github.com/bazelbuild/bazel-skylib/releases/tag/0.8.0) +skylib_version = "1.0.2" + +http_archive( + name = "bazel_skylib", + sha256 = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44", + type = "tar.gz", + url = "https://github.com/bazelbuild/bazel-skylib/releases/download/{}/bazel-skylib-{}.tar.gz".format(skylib_version, skylib_version), +) + +# For bazel_bsp aspect +http_archive( + name = "bazel_bsp", + strip_prefix = "bazel-bsp-09ef0c343c474bcad33a6a302dfd6a07cf37ea14", + url = "https://github.com/andrefmrocha/bazel-bsp/archive/09ef0c343c474bcad33a6a302dfd6a07cf37ea14.tar.gz", +) + +# For rules_scala +http_archive( + name = "io_bazel_rules_scala", + strip_prefix = "rules_scala-d6186617cfe64cef2074b23ca58daac75fe40d42", + url = "https://github.com/andrefmrocha/rules_scala/archive/d6186617cfe64cef2074b23ca58daac75fe40d42.tar.gz", +) + +load("@io_bazel_rules_scala//:version.bzl", "bazel_version") + +bazel_version(name = "bazel_version") + +load("@io_bazel_rules_scala//scala:toolchains.bzl", "scala_register_toolchains") + +scala_register_toolchains() + +load("@io_bazel_rules_scala//scala:scala.bzl", "scala_repositories") + +scala_repositories(( + "2.12.8", + { + "scala_compiler": "f34e9119f45abd41e85b9e121ba19dd9288b3b4af7f7047e86dc70236708d170", + "scala_library": "321fb55685635c931eba4bc0d7668349da3f2c09aee2de93a70566066ff25c28", + "scala_reflect": "4d6405395c4599ce04cea08ba082339e3e42135de9aae2923c9f5367e957315a", + }, +)) + +# For guava +http_archive( + name = "rules_jvm_external", + sha256 = "79c9850690d7614ecdb72d68394f994fef7534b292c4867ce5e7dec0aa7bdfad", + strip_prefix = "rules_jvm_external-2.8", + url = "https://github.com/bazelbuild/rules_jvm_external/archive/2.8.zip", +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") + +maven_install( + name = "maven", + artifacts = [ + "com.google.guava:guava:28.0-jre", + ], + fetch_sources = True, + repositories = [ + "https://maven.google.com", + "https://repo1.maven.org/maven2", + ], +) diff --git a/sample-repo/example/BUILD b/test-resources/action-graph-v2/example/BUILD similarity index 73% rename from sample-repo/example/BUILD rename to test-resources/action-graph-v2/example/BUILD index 747166352..bfc788153 100644 --- a/sample-repo/example/BUILD +++ b/test-resources/action-graph-v2/example/BUILD @@ -11,8 +11,13 @@ filegroup( scala_binary( name = "example", srcs = ["Example.scala"], + args = [ + "arg1", + "arg2", + ], + jvm_flags = ["-Xms2G -Xmx5G"], main_class = "example.Example", resources = [":resources"], + scalacopts = ["-target:jvm-1.8"], visibility = ["//visibility:public"], - deps = ["//dep"], ) diff --git a/sample-repo/example/Example.scala b/test-resources/action-graph-v2/example/Example.scala similarity index 100% rename from sample-repo/example/Example.scala rename to test-resources/action-graph-v2/example/Example.scala diff --git a/test-resources/cpp-project/.bazelrc b/test-resources/cpp-project/.bazelrc new file mode 100644 index 000000000..7766dc2e0 --- /dev/null +++ b/test-resources/cpp-project/.bazelrc @@ -0,0 +1,2 @@ +build --java_toolchain=@bazel_tools//tools/jdk:toolchain_java8 +build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java8 diff --git a/test-resources/cpp-project/.gitignore b/test-resources/cpp-project/.gitignore new file mode 100644 index 000000000..8837e07e0 --- /dev/null +++ b/test-resources/cpp-project/.gitignore @@ -0,0 +1,5 @@ +.bazelbsp/ +.bsp/ +.idea/ +.ijwb/ +bazel-* diff --git a/test-resources/cpp-project/WORKSPACE b/test-resources/cpp-project/WORKSPACE new file mode 100644 index 000000000..64930c8c6 --- /dev/null +++ b/test-resources/cpp-project/WORKSPACE @@ -0,0 +1,16 @@ +workspace(name = "cpp_test") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "com_google_googletest", + sha256 = "9a8a166eb6a56c7b3d7b19dc2c946fe4778fd6f21c7a12368ad3b836d8f1be48", + strip_prefix = "googletest-8567b09290fe402cf01923e2131c5635b8ed851b", + # Keep this URL in sync with ABSL_GOOGLETEST_COMMIT in ci/cmake_common.sh. + urls = ["https://github.com/google/googletest/archive/8567b09290fe402cf01923e2131c5635b8ed851b.zip"], # 2020-06-12T22:24:28Z +) + +local_repository( + name = "intellij_aspect", + path = "/home/andre/Projects/bazel-intellij/aspect", +) diff --git a/test-resources/cpp-project/example/BUILD b/test-resources/cpp-project/example/BUILD new file mode 100644 index 000000000..c0e8ab49b --- /dev/null +++ b/test-resources/cpp-project/example/BUILD @@ -0,0 +1,18 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary") + +cc_binary( + name = "example", + srcs = ["main.cpp"], + copts = [ + "-Iexternal/gtest/include", + ], + defines = [ + "BOOST_FALLTHROUGH", + ], + linkopts = [ + "-pthread", + ], + deps = [ + "@com_google_googletest//:gtest_main", + ], +) diff --git a/test-resources/cpp-project/example/main.cpp b/test-resources/cpp-project/example/main.cpp new file mode 100644 index 000000000..2c5b9fe1a --- /dev/null +++ b/test-resources/cpp-project/example/main.cpp @@ -0,0 +1,7 @@ +#include + +#include "gtest/gtest.h" + +int main(){ + std::cout << "Meias" << std::endl; +} diff --git a/test-resources/java-11-project/.bazelrc b/test-resources/java-11-project/.bazelrc new file mode 100644 index 000000000..7f6c0b136 --- /dev/null +++ b/test-resources/java-11-project/.bazelrc @@ -0,0 +1,2 @@ +build --java_toolchain=@bazel_tools//tools/jdk:toolchain_java11 +build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java11 diff --git a/test-resources/java-11-project/.gitignore b/test-resources/java-11-project/.gitignore new file mode 100644 index 000000000..18481deff --- /dev/null +++ b/test-resources/java-11-project/.gitignore @@ -0,0 +1,5 @@ +/.bazelbsp/ +/.bsp/ +/.idea/ +/.ijwb/ +/bazel-* \ No newline at end of file diff --git a/sample-repo/example/file.txt b/test-resources/java-11-project/WORKSPACE similarity index 100% rename from sample-repo/example/file.txt rename to test-resources/java-11-project/WORKSPACE diff --git a/test-resources/java-11-project/example/BUILD b/test-resources/java-11-project/example/BUILD new file mode 100644 index 000000000..ade8cb2f6 --- /dev/null +++ b/test-resources/java-11-project/example/BUILD @@ -0,0 +1,13 @@ +load("@rules_java//java:defs.bzl", "java_binary") + +java_binary( + name = "example", + srcs = ["Example.java"], + args = [ + "arg1", + "arg2", + ], + jvm_flags = ["-Xms2G -Xmx5G"], + main_class = "example.Example", + visibility = ["//visibility:public"], +) diff --git a/test-resources/java-11-project/example/Example.java b/test-resources/java-11-project/example/Example.java new file mode 100644 index 000000000..c0a927cf9 --- /dev/null +++ b/test-resources/java-11-project/example/Example.java @@ -0,0 +1,7 @@ +package example; + +public class Example { + public static void main(String[] args) { + System.out.println("Hello, this is a test!"); + } +} diff --git a/test-resources/java-8-project/.bazelrc b/test-resources/java-8-project/.bazelrc new file mode 100644 index 000000000..7766dc2e0 --- /dev/null +++ b/test-resources/java-8-project/.bazelrc @@ -0,0 +1,2 @@ +build --java_toolchain=@bazel_tools//tools/jdk:toolchain_java8 +build --host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java8 diff --git a/test-resources/java-8-project/.gitignore b/test-resources/java-8-project/.gitignore new file mode 100644 index 000000000..8837e07e0 --- /dev/null +++ b/test-resources/java-8-project/.gitignore @@ -0,0 +1,5 @@ +.bazelbsp/ +.bsp/ +.idea/ +.ijwb/ +bazel-* diff --git a/sample-repo/example/file2.txt b/test-resources/java-8-project/WORKSPACE similarity index 100% rename from sample-repo/example/file2.txt rename to test-resources/java-8-project/WORKSPACE diff --git a/test-resources/java-8-project/example/BUILD b/test-resources/java-8-project/example/BUILD new file mode 100644 index 000000000..ade8cb2f6 --- /dev/null +++ b/test-resources/java-8-project/example/BUILD @@ -0,0 +1,13 @@ +load("@rules_java//java:defs.bzl", "java_binary") + +java_binary( + name = "example", + srcs = ["Example.java"], + args = [ + "arg1", + "arg2", + ], + jvm_flags = ["-Xms2G -Xmx5G"], + main_class = "example.Example", + visibility = ["//visibility:public"], +) diff --git a/test-resources/java-8-project/example/Example.java b/test-resources/java-8-project/example/Example.java new file mode 100644 index 000000000..c0a927cf9 --- /dev/null +++ b/test-resources/java-8-project/example/Example.java @@ -0,0 +1,7 @@ +package example; + +public class Example { + public static void main(String[] args) { + System.out.println("Hello, this is a test!"); + } +} diff --git a/test-resources/java-default-project/.gitignore b/test-resources/java-default-project/.gitignore new file mode 100644 index 000000000..18481deff --- /dev/null +++ b/test-resources/java-default-project/.gitignore @@ -0,0 +1,5 @@ +/.bazelbsp/ +/.bsp/ +/.idea/ +/.ijwb/ +/bazel-* \ No newline at end of file diff --git a/test-resources/java-default-project/WORKSPACE b/test-resources/java-default-project/WORKSPACE new file mode 100644 index 000000000..e69de29bb diff --git a/test-resources/java-default-project/example/BUILD b/test-resources/java-default-project/example/BUILD new file mode 100644 index 000000000..ade8cb2f6 --- /dev/null +++ b/test-resources/java-default-project/example/BUILD @@ -0,0 +1,13 @@ +load("@rules_java//java:defs.bzl", "java_binary") + +java_binary( + name = "example", + srcs = ["Example.java"], + args = [ + "arg1", + "arg2", + ], + jvm_flags = ["-Xms2G -Xmx5G"], + main_class = "example.Example", + visibility = ["//visibility:public"], +) diff --git a/test-resources/java-default-project/example/Example.java b/test-resources/java-default-project/example/Example.java new file mode 100644 index 000000000..c0a927cf9 --- /dev/null +++ b/test-resources/java-default-project/example/Example.java @@ -0,0 +1,7 @@ +package example; + +public class Example { + public static void main(String[] args) { + System.out.println("Hello, this is a test!"); + } +} diff --git a/test-resources/sample-repo/.bazelversion b/test-resources/sample-repo/.bazelversion new file mode 100644 index 000000000..0b2eb36f5 --- /dev/null +++ b/test-resources/sample-repo/.bazelversion @@ -0,0 +1 @@ +3.7.2 diff --git a/test-resources/sample-repo/.gitignore b/test-resources/sample-repo/.gitignore new file mode 100644 index 000000000..18481deff --- /dev/null +++ b/test-resources/sample-repo/.gitignore @@ -0,0 +1,5 @@ +/.bazelbsp/ +/.bsp/ +/.idea/ +/.ijwb/ +/bazel-* \ No newline at end of file diff --git a/sample-repo/WORKSPACE b/test-resources/sample-repo/WORKSPACE similarity index 95% rename from sample-repo/WORKSPACE rename to test-resources/sample-repo/WORKSPACE index 612ce630c..95322e038 100644 --- a/sample-repo/WORKSPACE +++ b/test-resources/sample-repo/WORKSPACE @@ -55,8 +55,7 @@ http_archive( ) load("@io_bazel_rules_scala//scala:scala_maven_import_external.bzl", "scala_maven_import_external") -load("@io_bazel_rules_scala//scala:scala_cross_version.bzl", "default_scala_major_version", "scala_mvn_artifact") -load("@io_bazel_rules_scala//scala:scala_cross_version.bzl", "default_maven_server_urls") +load("@io_bazel_rules_scala//scala:scala_cross_version.bzl", "default_maven_server_urls", "default_scala_major_version", "scala_mvn_artifact") # Exp scala_maven_import_external( diff --git a/test-resources/sample-repo/dep/BUILD b/test-resources/sample-repo/dep/BUILD new file mode 100644 index 000000000..098fdbaf1 --- /dev/null +++ b/test-resources/sample-repo/dep/BUILD @@ -0,0 +1,23 @@ +load("@rules_java//java:defs.bzl", "java_binary") +load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library") + +scala_library( + name = "dep", + srcs = glob([ + "*.java", + "*.scala", + ]), + visibility = ["//visibility:public"], + deps = ["//dep/deeper"], +) + +java_binary( + name = "java-dep", + srcs = glob(["*.java"]), + javacopts = [ + "-Werror", + "-Xlint:all", + ], + main_class = "dep.JavaTest", + visibility = ["//visibility:public"], +) diff --git a/sample-repo/dep/Dep.scala b/test-resources/sample-repo/dep/Dep.scala similarity index 100% rename from sample-repo/dep/Dep.scala rename to test-resources/sample-repo/dep/Dep.scala diff --git a/sample-repo/dep/JavaTest.java b/test-resources/sample-repo/dep/JavaTest.java similarity index 100% rename from sample-repo/dep/JavaTest.java rename to test-resources/sample-repo/dep/JavaTest.java diff --git a/sample-repo/dep/Test.scala b/test-resources/sample-repo/dep/Test.scala similarity index 100% rename from sample-repo/dep/Test.scala rename to test-resources/sample-repo/dep/Test.scala diff --git a/sample-repo/dep/deeper/BUILD b/test-resources/sample-repo/dep/deeper/BUILD similarity index 100% rename from sample-repo/dep/deeper/BUILD rename to test-resources/sample-repo/dep/deeper/BUILD diff --git a/sample-repo/dep/deeper/DeeperTest.scala b/test-resources/sample-repo/dep/deeper/DeeperTest.scala similarity index 100% rename from sample-repo/dep/deeper/DeeperTest.scala rename to test-resources/sample-repo/dep/deeper/DeeperTest.scala diff --git a/test-resources/sample-repo/example/BUILD b/test-resources/sample-repo/example/BUILD new file mode 100644 index 000000000..5129c06a5 --- /dev/null +++ b/test-resources/sample-repo/example/BUILD @@ -0,0 +1,33 @@ +load("@io_bazel_rules_scala//scala:scala.bzl", "scala_binary", "scala_test") + +filegroup( + name = "resources", + srcs = [ + "file.txt", + "file2.txt", + ], +) + +scala_binary( + name = "example", + srcs = ["Example.scala"], + args = [ + "arg1", + "arg2", + ], + jvm_flags = ["-Xms2G -Xmx5G"], + main_class = "example.Example", + resources = [":resources"], + scalacopts = ["-target:jvm-1.8"], + visibility = ["//visibility:public"], + deps = ["//dep"], +) + +scala_test( + name = "example-test", + srcs = ["ExampleTest.scala"], + main_class = "example.ExampleTest", + resources = [":resources"], + visibility = ["//visibility:public"], + deps = ["//dep"], +) diff --git a/test-resources/sample-repo/example/Example.scala b/test-resources/sample-repo/example/Example.scala new file mode 100644 index 000000000..b7308d9f2 --- /dev/null +++ b/test-resources/sample-repo/example/Example.scala @@ -0,0 +1,11 @@ +package example + +import dep.Dep +import java.util.ArrayList + +object Example { + def main(args: Array[String]): Unit = { + val s = "Sup" + Dep.list.head + println(s) + } +} diff --git a/test-resources/sample-repo/example/ExampleTest.scala b/test-resources/sample-repo/example/ExampleTest.scala new file mode 100644 index 000000000..dd96c1da1 --- /dev/null +++ b/test-resources/sample-repo/example/ExampleTest.scala @@ -0,0 +1,10 @@ +package example + +import dep.Dep + +object ExampleTest { + def main(args: Array[String]): Unit = { + val s = "Sup" + Dep.list.head + println(s) + } +} diff --git a/test-resources/sample-repo/example/file.txt b/test-resources/sample-repo/example/file.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test-resources/sample-repo/example/file2.txt b/test-resources/sample-repo/example/file2.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test-resources/sample-repo/target_without_args/BUILD b/test-resources/sample-repo/target_without_args/BUILD new file mode 100644 index 000000000..2a080bab2 --- /dev/null +++ b/test-resources/sample-repo/target_without_args/BUILD @@ -0,0 +1,8 @@ +load("@io_bazel_rules_scala//scala:scala.bzl", "scala_binary") + +scala_binary( + name = "binary", + srcs = ["Example.scala"], + jvm_flags = ["-Xms2G -Xmx5G"], + main_class = "example.Example", +) diff --git a/test-resources/sample-repo/target_without_args/Example.scala b/test-resources/sample-repo/target_without_args/Example.scala new file mode 100644 index 000000000..a37b1881c --- /dev/null +++ b/test-resources/sample-repo/target_without_args/Example.scala @@ -0,0 +1,7 @@ +package target_without_args + +import java.util.ArrayList + +object Example { + def main(args: Array[String]): Unit = {} +} diff --git a/test-resources/sample-repo/target_without_jvm_flags/BUILD b/test-resources/sample-repo/target_without_jvm_flags/BUILD new file mode 100644 index 000000000..51415a133 --- /dev/null +++ b/test-resources/sample-repo/target_without_jvm_flags/BUILD @@ -0,0 +1,11 @@ +load("@io_bazel_rules_scala//scala:scala.bzl", "scala_binary") + +scala_binary( + name = "binary", + srcs = ["Example.scala"], + args = [ + "arg1", + "arg2", + ], + main_class = "example.Example", +) diff --git a/test-resources/sample-repo/target_without_jvm_flags/Example.scala b/test-resources/sample-repo/target_without_jvm_flags/Example.scala new file mode 100644 index 000000000..17489724e --- /dev/null +++ b/test-resources/sample-repo/target_without_jvm_flags/Example.scala @@ -0,0 +1,7 @@ +package target_without_jvm_flags + +import java.util.ArrayList + +object Example { + def main(args: Array[String]): Unit = {} +} diff --git a/test-resources/sample-repo/target_without_main_class/BUILD b/test-resources/sample-repo/target_without_main_class/BUILD new file mode 100644 index 000000000..59a002a2f --- /dev/null +++ b/test-resources/sample-repo/target_without_main_class/BUILD @@ -0,0 +1,6 @@ +load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library") + +scala_library( + name = "library", + srcs = ["Example.scala"], +) diff --git a/test-resources/sample-repo/target_without_main_class/Example.scala b/test-resources/sample-repo/target_without_main_class/Example.scala new file mode 100644 index 000000000..7386fbeb4 --- /dev/null +++ b/test-resources/sample-repo/target_without_main_class/Example.scala @@ -0,0 +1,3 @@ +package target_without_main_class + +object Example {} diff --git a/test.sh b/test.sh index 972027f24..df1def015 100755 --- a/test.sh +++ b/test.sh @@ -1,16 +1,67 @@ #!/usr/bin/env bash -dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) -echo $dir -. "${dir}"/test_runner.sh +NC='\033[0m' +GREEN='\033[0;32m' +RED='\033[0;31m' +TEST_PROJECTS=("sample-repo" "action-graph-v2" "java-8-project" "java-11-project" "java-default-project" "cpp-project") + +log_test_progress() { + echo -e "\n[TEST] $*" +} + +run_test() { + set +e + + SECONDS=0 + TEST_ARG=$* + + log_test_progress "Running \"$TEST_ARG\"" + + $TEST_ARG + EXECUTION_CODE=$? + DURATION=$SECONDS + + if [ $EXECUTION_CODE -eq 0 ]; then + log_test_progress "${GREEN}Test \"$TEST_ARG\" successful ($DURATION sec) $NC" + else + log_test_progress "${RED}Test \"$TEST_ARG\" failed $NC ($DURATION sec) $NC" + exit $EXECUTION_CODE + fi + + set -e +} test_bsp_server() { - bazel build //main/src/org/jetbrains/bsp/bazel:bsp-install - bsp_path="$(bazel info bazel-bin)/main/src/org/jetbrains/bsp/bazel/bsp-install" - cd sample-repo + log_test_progress "Publishing project..." + + bazel build //src/main/java/org/jetbrains/bsp/bazel:bsp-install + bsp_path="$(bazel info bazel-bin)/src/main/java/org/jetbrains/bsp/bazel/bsp-install" + + log_test_progress "Installing BSP..." + $bsp_path + + cd test-resources + + for project in "${TEST_PROJECTS[@]}" + do + cd "$project" + $bsp_path + cd .. + done cd .. - bazel run //main/test/org/jetbrains/bsp/bazel:bsp-test + + log_test_progress "Environment has been prepared!" + log_test_progress "Running integration test..." + bazel run //src/test/java/org/jetbrains/bsp/bazel:bsp-integration-test } + +log_test_progress "Running BSP tests..." +echo -e "===================================\n" + run_test test_bsp_server + +echo -e "\n\n===================================" +echo -e "===================================" +log_test_progress "${GREEN}All test passed!" diff --git a/test_runner.sh b/test_runner.sh deleted file mode 100755 index 28cc8749a..000000000 --- a/test_runner.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -# -# Test runner. - -NC='\033[0m' -GREEN='\033[0;32m' -RED='\033[0;31m' - -run_test() { - set +e - SECONDS=0 - TEST_ARG=$@ - echo "running test $TEST_ARG" - RES=$($TEST_ARG 2>&1) - RESPONSE_CODE=$? - DURATION=$SECONDS - if [ $RESPONSE_CODE -eq 0 ]; then - echo -e "${GREEN} Test \"$TEST_ARG\" successful ($DURATION sec) $NC" - else - printf "\nLog:\n" - echo "$RES" - echo -e "${RED} Test \"$TEST_ARG\" failed $NC ($DURATION sec) $NC" - exit $RESPONSE_CODE - fi - set -e -} diff --git a/third_party.bzl b/third_party.bzl index 007cbfe8b..c2951d5af 100644 --- a/third_party.bzl +++ b/third_party.bzl @@ -1,7 +1,6 @@ load("@rules_jvm_external//:specs.bzl", "maven", "parse") load("@rules_jvm_external//:defs.bzl", "maven_install") load("@io_bazel_rules_scala//scala:scala_cross_version.bzl", "default_maven_server_urls") -load("@io_bazel_rules_scala//scala:scala_maven_import_external.bzl", "scala_maven_import_external") def _dependency(coordinates, exclusions = None): artifact = parse.parse_maven_coordinate(coordinates) @@ -17,10 +16,16 @@ def _dependency(coordinates, exclusions = None): _deps = [ _dependency("com.google.code.gson:gson:2.8.5"), _dependency("com.google.guava:guava:28.1-jre"), - _dependency("ch.epfl.scala:bsp4j:2.0.0-M12+27-4994bd9d-SNAPSHOT"), - _dependency("ch.epfl.scala:bsp-testkit_2.13:2.0.0-M12+25-e4df1538-SNAPSHOT"), + _dependency("ch.epfl.scala:bsp4j:2.0.0-M14"), + _dependency("ch.epfl.scala:bsp-testkit_2.13:2.0.0-M14"), _dependency("org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.8.0"), _dependency("org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.19.0"), + _dependency("commons-cli:commons-cli:jar:1.4"), + _dependency("org.jetbrains:annotations:jar:20.1.0"), + _dependency("io.vavr:vavr:0.9.0"), + _dependency("org.apache.logging.log4j:log4j-api:2.6.1"), + _dependency("org.apache.logging.log4j:log4j-core:2.6.1"), + _dependency("junit:junit:4.12"), ] def dependencies():