Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More Workspace Info #98

Merged
merged 12 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("//private:load_tool.bzl", "load_tool")
load("//private:bsp_workspace_info.bzl", _bsp_workspace_info = "bsp_workspace_info")

# <--- Updated automatically by release job
_bsp4bazel_version = "0.0.30"
Expand All @@ -23,4 +24,10 @@ def _bsp4bazel_load(platform):

def bsp4bazel_setup():
_bsp4bazel_load("linux-x86")
_bsp4bazel_load("macos-x86")
_bsp4bazel_load("macos-x86")

def bsp_workspace_info(name = "bsp_workspace_info"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this require a name, but set it as default, then fail if it has been changed?

if (name != "bsp_workspace_info"):
fail("name must be 'bsp_workspace_info'")

_bsp_workspace_info(name = name)
22 changes: 1 addition & 21 deletions bazel_rules/bsp_target_info_aspect.bzl
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
load("@io_bazel_rules_scala//scala:semanticdb_provider.bzl", "SemanticdbInfo")
load("@io_bazel_rules_scala_config//:config.bzl", "SCALA_VERSION")
load("@io_bazel_rules_scala//scala/private/toolchain_deps:toolchain_deps.bzl", "find_deps_info_on")

def _collect_src_files(srcs):
return [
Expand All @@ -25,44 +23,26 @@ def bsp_target_info_aspect_impl(target, ctx):
toolchain = ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"]
classpath = [file.path for file in target[JavaInfo].transitive_compile_time_jars.to_list()]

compile_classpath = [
file
for deps in find_deps_info_on(ctx, "@io_bazel_rules_scala//scala:toolchain_type", "scala_compile_classpath").deps
for file in deps[JavaInfo].compile_jars.to_list()
]

# Why not use the class_jar in SemanticdbInfo? Because it's a string, not a File, so we can't pass
# pass it to outputs.
# https://github.com/bazelbuild/rules_scala/issues/1527
semanticdb_classpath = [
file
for deps in find_deps_info_on(ctx, "@io_bazel_rules_scala//scala:toolchain_type", "semanticdb").deps
for file in deps[JavaInfo].compile_jars.to_list()
]

src_files = []
if hasattr(ctx.rule.attr, "srcs"):
src_files = _collect_src_files(ctx.rule.attr.srcs)
if hasattr(ctx.rule.attr, "resources"):
src_files = src_files + _collect_src_files(ctx.rule.attr.resources)

output_struct = struct(
scala_version = SCALA_VERSION,
scalac_options = toolchain.scalacopts,
classpath = classpath,
scala_compiler_jars = [file.path for file in compile_classpath],
srcs = src_files,
target_label = str(target.label),
semanticdb_target_root = target[SemanticdbInfo].target_root,
semanticdb_pluginjar = [file.path for file in semanticdb_classpath],
)

json_output_file = ctx.actions.declare_file("%s_bsp_target_info.json" % target.label.name)
ctx.actions.write(json_output_file, json.encode_indent(output_struct))

return [
OutputGroupInfo(
bsp_output = depset([json_output_file] + semanticdb_classpath + compile_classpath),
bsp_output = depset([json_output_file]),
),
]

Expand Down
62 changes: 62 additions & 0 deletions bazel_rules/private/bsp_workspace_info.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
load("@io_bazel_rules_scala//scala/private/toolchain_deps:toolchain_deps.bzl", "find_deps_info_on")
load("@io_bazel_rules_scala_config//:config.bzl", "SCALA_VERSION")

def _copy_files(ctx, files):
output_files = []

for file in files:
output_file = ctx.actions.declare_file(ctx.attr.name + "_" + file.basename)

ctx.actions.run_shell(
inputs = [file],
outputs = [output_file],
command = "cp {src} {out}".format(src = file.path, out = output_file.path),
)

output_files.append(output_file)

return output_files

def _bsp_workspace_info_impl(ctx):
"""
Outputs metadata about the BSP workspace, as well as copying depenendies to output (so that downstream tools can use them)

Args:
ctx: The context object.

Returns:
A list of dependencies for the BSP workspace.
"""

compile_classpath = [
file
for deps in find_deps_info_on(ctx, "@io_bazel_rules_scala//scala:toolchain_type", "scala_compile_classpath").deps
for file in deps[JavaInfo].compile_jars.to_list()
]

# Why not use the class_jar in SemanticdbInfo? Because it's a string, not a File, so we can't pass
# pass it to outputs.
# https://github.com/bazelbuild/rules_scala/issues/1527
semanticdb_classpath = [
file
for deps in find_deps_info_on(ctx, "@io_bazel_rules_scala//scala:toolchain_type", "semanticdb").deps
for file in deps[JavaInfo].compile_jars.to_list()
]

scalac_output_files = _copy_files(ctx, compile_classpath)
semanticdb_output_files = _copy_files(ctx, semanticdb_classpath)

json_output_file = ctx.actions.declare_file(ctx.attr.name + ".json")
output_struct = struct(
scala_version = SCALA_VERSION,
scalac_deps = [file.path for file in scalac_output_files],
semanticdb_dep = [file.path for file in semanticdb_output_files][0],
)
ctx.actions.write(json_output_file, json.encode_indent(output_struct))

return [DefaultInfo(files = depset(scalac_output_files + semanticdb_output_files + [json_output_file]))]

bsp_workspace_info = rule(
implementation = _bsp_workspace_info_impl,
toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"],
)
23 changes: 20 additions & 3 deletions bazel_rules/test/rules.test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class BazelRulesTest extends munit.FunSuite:

def testBazelAspect[T](dir: os.Path)(implicit loc: munit.Location): Unit = {
def testBazelRules[T](dir: os.Path)(implicit loc: munit.Location): Unit = {
test(s"should output bsp_target_info.json for $dir") {
os.proc(
dir / "bazel",
Expand All @@ -20,6 +20,23 @@ class BazelRulesTest extends munit.FunSuite:
assert(os.exists(dir / "bazel-bin" / "src" / "example" / "example_bsp_target_info.json"))
assert(os.exists(dir / "bazel-bin" / "src" / "example" / "foo" / "foo_bsp_target_info.json"))
}

test(s"should output bsp_workspace_info.json for $dir") {
os.proc(
dir / "bazel",
"build",
"//:bsp_workspace_info",
).call(cwd = dir)

assert(os.exists(dir / "bazel-bin" / "bsp_workspace_info.json"))

val json = os.read(dir / "bazel-bin" / "bsp_workspace_info.json")
assert(json.contains("2.12.18"), "No scala version")
assert(json.contains("semanticdb-scalac"), "No semanticdb dep")
assert(json.contains("scala-reflect"), "No scala-reflect dep")
assert(json.contains("scala-library"), "No scala-library dep")
assert(json.contains("scala-compiler"), "No scala-compiler dep")
}
}

test("should build examples/simple-no-errors") {
Expand All @@ -39,5 +56,5 @@ class BazelRulesTest extends munit.FunSuite:
assertEquals(result.exitCode, 1)
}

testBazelAspect(os.pwd / "examples" / "simple-no-errors")
testBazelAspect(os.pwd / "examples" / "simple-with-errors")
testBazelRules(os.pwd / "examples" / "simple-no-errors")
testBazelRules(os.pwd / "examples" / "simple-with-errors")
3 changes: 3 additions & 0 deletions examples/simple-no-errors/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
load("@bsp4bazel-rules//:bsp4bazel.bzl", "bsp_workspace_info")

bsp_workspace_info()
2 changes: 1 addition & 1 deletion examples/simple-no-errors/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,6 @@ local_repository(
path = "../../bazel_rules",
)

load("@bsp4bazel-rules//:bsp4bazel_setup.bzl", "bsp4bazel_setup")
load("@bsp4bazel-rules//:bsp4bazel.bzl", "bsp4bazel_setup")

bsp4bazel_setup()
3 changes: 3 additions & 0 deletions examples/simple-with-errors/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
load("@bsp4bazel-rules//:bsp4bazel.bzl", "bsp_workspace_info")

bsp_workspace_info()
2 changes: 1 addition & 1 deletion examples/simple-with-errors/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ local_repository(
path = "../../bazel_rules",
)

load("@bsp4bazel-rules//:bsp4bazel_setup.bzl", "bsp4bazel_setup")
load("@bsp4bazel-rules//:bsp4bazel.bzl", "bsp4bazel_setup")

bsp4bazel_setup()
17 changes: 11 additions & 6 deletions src/main/scala/bazeltools/bsp4bazel/BazelBspServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ class Bsp4BazelServer(
state <- stateRef.get
workspaceRoot <- state.workspaceRoot.asIO
runner <- state.bspTaskRunner.asIO
workspaceInfo <- runner.workspaceInfo
targets <- buildTargets(logger, workspaceRoot, runner)
_ <- stateRef.update(s =>
s.copy(
targets = Some(targets),
targetSourceMap = Bsp4BazelServer.TargetSourceMap(targets)
targetSourceMap = Bsp4BazelServer.TargetSourceMap(targets),
workspaceInfo = Some(workspaceInfo)
)
)
yield ()
Expand All @@ -85,8 +87,9 @@ class Bsp4BazelServer(
for
_ <- logger.info("workspace/buildTargets")
state <- stateRef.get
targets <- state.targets.asIO
yield WorkspaceBuildTargetsResult(targets.map(_.asBuildTarget))
workspaceInfo <- state.workspaceInfo.asIO
bspTargets <- state.targets.asIO
yield WorkspaceBuildTargetsResult(bspTargets.map(_.asBuildTarget(workspaceInfo)))

def buildTargetInverseSources(
params: InverseSourcesParams
Expand All @@ -107,6 +110,7 @@ class Bsp4BazelServer(
for
_ <- logger.info("buildTarget/scalacOptions")
state <- stateRef.get
workspaceInfo <- state.workspaceInfo.asIO
bspTargets <- state.targets.asIO
yield
val select = params.targets.toSet
Expand All @@ -115,7 +119,7 @@ class Bsp4BazelServer(
filtered.size == params.targets.size,
"Some of the requested targets didn't exist. Shouldn't be possible"
)
ScalacOptionsResult(filtered.map(_.asScalaOptionItem))
ScalacOptionsResult(filtered.map(_.asScalaOptionItem(workspaceInfo)))

private def buildTargetScalacOption(
target: BuildTargetIdentifier
Expand Down Expand Up @@ -298,11 +302,12 @@ object Bsp4BazelServer:
currentErrors: List[PublishDiagnosticsParams],
workspaceRoot: Option[Path],
bspTaskRunner: Option[BspTaskRunner],
targets: Option[List[BspTaskRunner.BspTarget]]
targets: Option[List[BspTaskRunner.BspTarget]],
workspaceInfo: Option[BspTaskRunner.WorkspaceInfo]
)

def defaultState: ServerState =
ServerState(Bsp4BazelServer.TargetSourceMap.empty, Nil, None, None, None)
ServerState(Bsp4BazelServer.TargetSourceMap.empty, Nil, None, None, None, None)

def create(
client: BspClient,
Expand Down
9 changes: 9 additions & 0 deletions src/main/scala/bazeltools/bsp4bazel/protocol/Models.scala
Original file line number Diff line number Diff line change
Expand Up @@ -629,3 +629,12 @@ case class CompileReport(
)
object CompileReport:
given Codec[CompileReport] = deriveCodec[CompileReport]


object CommonCodecs:
given pathCodec: Codec[Path] = Codec.from(
Decoder[String].emap { s =>
Either.catchNonFatal(Paths.get(s)).leftMap(_.getMessage)
},
Encoder[String].contramap(_.toString)
)
Loading