Skip to content

Commit

Permalink
Add mezel support (#28)
Browse files Browse the repository at this point in the history
* fix: implementation of `enable_semanticdb` attribute

* chore: add TODO +reformat scala3 dir

* feat: add `semanticdb_bundle_in_jar` attribute to toolchain

Also, semanticdb generation has been moved to the zinc compile phase

* feat: add `scala_version` attribute to `scala_toolchain`

* feat: add compatibility for rules with the `scala` attribute

* feat: add mezel compatibility rules

* fix: add generation of stub diagnostics
  • Loading branch information
name-snrl authored Dec 18, 2023
1 parent 276a48e commit 7abf6c6
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 20 deletions.
13 changes: 13 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ maven_install(
"org.scala-sbt:librarymanagement-core_3:2.0.0-alpha12",
"org.scala-sbt:librarymanagement-coursier_3:2.0.0-alpha6",
],
fetch_sources = True,
maven_install_json = "//:deps_install.json",
repositories = [
"https://repo1.maven.org/maven2",
Expand Down Expand Up @@ -54,6 +55,18 @@ load("//3rdparty:workspace.bzl", "maven_dependencies")

maven_dependencies()

load("//mezel_compatibility:repositories.bzl", "mezel_compatibility_repository")

mezel_compatibility_repository(
name = "mezel",
mezel_version = "216327ab2fc6d5866f13ace1bf75c9d1abdcd8a6",
sha256 = "dbdb144fc943670dc1b715629f939d8f5010ae1b2ab889b3620866ce19cda1df",
)

load("@mezel//rules:load_mezel.bzl", "load_mezel")

load_mezel()

load("//rules/scalafmt:workspace.bzl", "scalafmt_default_config", "scalafmt_repositories")

scalafmt_repositories()
Expand Down
Empty file added mezel_compatibility/BUILD.bazel
Empty file.
178 changes: 178 additions & 0 deletions mezel_compatibility/aspect.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
"Mezel aspect compatible with `rules_scala3`"

load("@rules_scala3//rules:providers.bzl", "ScalaInfo", "SemanticdbInfo")

BuildTargetInfo = provider(
doc = "Generate depset of bypassed targets.",
fields = {
"output": "output",
},
)

# should only apply to rules that generate semanticdb (phase_zinc_compile)
# also all rules must contain attrs, which are used here

#"scala_library",
#"scala_binary",
#"scala_test",

#"scalajs_library", # may not work as is
#"scalajs_link", # may not work as is

def _mezel_aspect_impl(target, ctx):
# TODO check rules
#print(ctx.rule.kind)

jdk = ctx.attr._jdk
target_attrs = ctx.rule.attr
toolchain = target[ScalaInfo].toolchain

if not toolchain.enable_semanticdb:
fail("SemanticDB is not enabled, please set the `enable_semanticdb` attribute to `True` in your `scala_toolchain`", toolchain)

if toolchain.semanticdb_bundle_in_jar:
fail("SemanticDB is bundled in the output jar, please generate it separately by setting the `semanticdb_bundle_in_jar` attribute to `False` in your `scala_toolchain`")

toolchain_opts = toolchain.global_scalacopts if toolchain.global_scalacopts else []
target_opts = target_attrs.scalacopts if target_attrs.scalacopts else []

opts = toolchain_opts + target_opts

compiler_version = toolchain.scala_version

semanticdb_target_root = target[SemanticdbInfo].target_root

scala_compile_classpath = [
file
for target in toolchain.compiler_classpath
for file in target[JavaInfo].compile_jars.to_list()
]

dep_outputs = [
dependency[BuildTargetInfo].output
for dependency in target_attrs.deps
if BuildTargetInfo in dependency
]
direct_dep_labels = [dependency.label for dependency in dep_outputs]

transitive_labels = depset(
[target.label],
transitive = [x.transitive_labels for x in dep_outputs],
)
ignore = transitive_labels.to_list()

output_class_jars = [x.class_jar.path for x in target[JavaInfo].java_outputs]
if (len(output_class_jars) != 1):
fail("Expected exactly one output class jar, got {}".format(output_class_jars))
output_class_jar = output_class_jars[0]

transitive_compile_jars = target[JavaInfo].transitive_compile_time_jars.to_list()
cp_jars = [x.path for x in transitive_compile_jars if x.owner != target.label]
transitive_source_jars = target[JavaInfo].transitive_source_jars.to_list()
src_jars = [x.path for x in transitive_source_jars if x.owner not in ignore]

raw_plugins = target_attrs.plugins if target_attrs.plugins else []
plugins = [
y.path
for x in raw_plugins
if JavaInfo in x
for y in x[JavaInfo].compile_jars.to_list()
]

# generate bsp_info files
scalac_options_file = ctx.actions.declare_file("{}_bsp_scalac_options.json".format(target.label.name))
scalac_options_content = struct(
scalacopts = opts,
semanticdbPlugin = "",
plugins = plugins,
classpath = cp_jars,
targetroot = semanticdb_target_root,
outputClassJar = output_class_jar,
compilerVersion = compiler_version,
)
ctx.actions.write(scalac_options_file, json.encode(scalac_options_content))

sources_file = ctx.actions.declare_file("{}_bsp_sources.json".format(target.label.name))
sources_content = struct(
sources = [
f.path
for src in target_attrs.srcs
for f in src.files.to_list()
],
)
ctx.actions.write(sources_file, json.encode(sources_content))

dependency_sources_file = ctx.actions.declare_file("{}_bsp_dependency_sources.json".format(target.label.name))
dependency_sources_content = struct(
sourcejars = src_jars,
)
ctx.actions.write(dependency_sources_file, json.encode(dependency_sources_content))

build_target_file = ctx.actions.declare_file("{}_bsp_build_target.json".format(target.label.name))
build_target_content = struct(
javaHome = jdk[java_common.JavaRuntimeInfo].java_home,
scalaCompilerClasspath = [x.path for x in scala_compile_classpath],
compilerVersion = compiler_version,
deps = [str(label) for label in direct_dep_labels],
directory = target.label.package,
)
ctx.actions.write(build_target_file, json.encode(build_target_content))

ctx.actions.do_nothing(
mnemonic = "MezelAspect",
inputs = [scalac_options_file, sources_file, dependency_sources_file, build_target_file],
)

files = struct(
label = target.label,
transitive_labels = transitive_labels,
)

transitive_output_files = [
dependency[OutputGroupInfo].bsp_info
for dependency in target_attrs.deps
if OutputGroupInfo in dependency and hasattr(dependency[OutputGroupInfo], "bsp_info")
]

# TODO: add .diagnosticsproto generation to rules and it would be
# nice to move this to a separate phase along with semanticdb generation
diagnostics = ctx.actions.declare_file("{}.diagnosticsproto".format(ctx.label.name))
ctx.actions.run_shell(
mnemonic = "Scalac",
command = "touch $1",
arguments = [diagnostics.path],
outputs = [diagnostics],
)

return [
OutputGroupInfo(
bsp_info = depset(
[scalac_options_file, sources_file, dependency_sources_file, build_target_file, diagnostics],
transitive = transitive_output_files,
),
bsp_info_deps = depset(
scala_compile_classpath,
transitive = [
target[JavaInfo].transitive_compile_time_jars,
target[JavaInfo].transitive_source_jars,
] + [x[JavaInfo].compile_jars for x in raw_plugins],
),
),
BuildTargetInfo(output = files),
]

mezel_aspect = aspect(
implementation = _mezel_aspect_impl,
attr_aspects = ["deps"],
required_aspect_providers = [[JavaInfo, SemanticdbInfo]],
required_providers = [
[JavaInfo, ScalaInfo], # allow non-semanticdb targets to throw an error
[JavaInfo, SemanticdbInfo, ScalaInfo],
],
attrs = {
"_jdk": attr.label(
default = Label("@bazel_tools//tools/jdk:current_java_runtime"),
providers = [java_common.JavaRuntimeInfo],
),
},
)
31 changes: 31 additions & 0 deletions mezel_compatibility/repositories.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Repository rule for loading mazel repository"""

def _mezel_compatibility_repository_impl(repository_ctx):
repository_ctx.download_and_extract(
url = "https://github.com/valdemargr/mezel/archive/%s.zip" % repository_ctx.attr.mezel_version,
sha256 = repository_ctx.attr.sha256,
type = "zip",
stripPrefix = "mezel-%s" % repository_ctx.attr.mezel_version,
)
replacement_path = "aspects/aspect.bzl"
repository_ctx.delete(replacement_path)
repository_ctx.file(
replacement_path,
content = repository_ctx.read(Label("//mezel_compatibility:aspect.bzl")),
executable = False,
)

mezel_compatibility_repository = repository_rule(
doc = "A repository rule that loads the mazel repository, adding compatibility with `rules_scala3`.",
attrs = {
"mezel_version": attr.string(
doc = "The latest version can be found in the README of the upstream repository.",
mandatory = True,
),
"sha256": attr.string(
doc = "The hash of the latest version can be found in the README of the upstream repository.",
mandatory = True,
),
},
implementation = _mezel_compatibility_repository_impl,
)
17 changes: 13 additions & 4 deletions rules/common/private/get_toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ load(
_ZincConfiguration = "ZincConfiguration",
)

def _gen_toolchain(scala):
def _gen_toolchain(scala, toolchain):
toolchain_info = platform_common.ToolchainInfo(
scala_version = scala[_ScalaConfiguration].version,
enable_semanticdb = toolchain.enable_semanticdb,
semanticdb_bundle_in_jar = toolchain.semanticdb_bundle_in_jar,
is_zinc = True if _ZincConfiguration in scala else False,
zinc_log_level = scala[_ZincConfiguration].log_level if _ZincConfiguration in scala else None,
compiler_bridge = scala[_ZincConfiguration].compiler_bridge if _ZincConfiguration in scala else None,
Expand Down Expand Up @@ -42,9 +45,15 @@ def get_toolchain(ctx):
"""

if getattr(ctx.attr, "_worker_rule", False):
return _gen_toolchain(ctx.attr._scala)
stub_toolchain = platform_common.ToolchainInfo(
enable_semanticdb = False,
semanticdb_bundle_in_jar = False,
)
return _gen_toolchain(ctx.attr._scala, stub_toolchain)

toolchain = ctx.toolchains["@rules_scala3//scala3:toolchain_type"]

if getattr(ctx.attr, "scala", False):
return _gen_toolchain(ctx.attr.scala)
return _gen_toolchain(ctx.attr.scala, toolchain)

return ctx.toolchains["@rules_scala3//scala3:toolchain_type"]
return toolchain
34 changes: 33 additions & 1 deletion rules/private/phases/phase_zinc_compile.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
load("@bazel_skylib//lib:paths.bzl", "paths")
load(
"@rules_scala3//rules:providers.bzl",
_SemanticdbInfo = "SemanticdbInfo",
_ZincInfo = "ZincInfo",
)
load(
Expand All @@ -17,6 +19,35 @@ load("//rules/common:private/get_toolchain.bzl", "get_toolchain")
def phase_zinc_compile(ctx, g):
toolchain = get_toolchain(ctx)

semanticdb_files = []
semanticdb_scalacopts = []
if toolchain.enable_semanticdb:
semanticdb_scalacopts.append("-Xsemanticdb")

target_output_path = paths.dirname(ctx.outputs.jar.path)

semanticdb_intpath = "_scalac/" + ctx.label.name + "/classes" if toolchain.semanticdb_bundle_in_jar == True else "_semanticdb/" + ctx.label.name

semanticdb_target_root = "%s/%s" % (target_output_path, semanticdb_intpath)

if not toolchain.semanticdb_bundle_in_jar:
semanticdb_scalacopts.append("-semanticdb-target:" + semanticdb_target_root)

# declare all the semanticdb files
semanticdb_outpath = "META-INF/semanticdb"

for source_file in ctx.files.srcs:
if source_file.extension == "scala":
output_filename = "%s/%s/%s.semanticdb" % (semanticdb_intpath, semanticdb_outpath, source_file.path)
semanticdb_files.append(ctx.actions.declare_file(output_filename))

semanticdb_info = _SemanticdbInfo(
semanticdb_enabled = True,
target_root = None if toolchain.semanticdb_bundle_in_jar else semanticdb_target_root,
is_bundled_in_jar = toolchain.semanticdb_bundle_in_jar,
)
g.out.providers.append(semanticdb_info)

apis = ctx.actions.declare_file("{}/apis.gz".format(ctx.label.name))
infos = ctx.actions.declare_file("{}/infos.gz".format(ctx.label.name))
mains_file = ctx.actions.declare_file("{}.jar.mains.txt".format(ctx.label.name))
Expand All @@ -43,6 +74,7 @@ def phase_zinc_compile(ctx, g):
args.add_all(g.classpaths.compile, format_each = "--cp=%s")
args.add_all(toolchain.global_scalacopts, format_each = "--compiler_option=%s")
args.add_all(ctx.attr.scalacopts, format_each = "--compiler_option=%s")
args.add_all(semanticdb_scalacopts, format_each = "--compiler_option=%s")
args.add_all(javacopts, format_each = "--java_compiler_option=%s")
args.add(ctx.label, format = "--label=%s")
args.add("--main_manifest", mains_file)
Expand Down Expand Up @@ -74,7 +106,7 @@ def phase_zinc_compile(ctx, g):
] + [zinc.deps_files for zinc in zincs],
)

outputs = [g.classpaths.jar, mains_file, apis, infos, relations, setup, stamps, used, tmp]
outputs = [g.classpaths.jar, mains_file, apis, infos, relations, setup, stamps, used, tmp] + semanticdb_files

# todo: different execution path for nosrc jar?
ctx.actions.run(
Expand Down
8 changes: 8 additions & 0 deletions rules/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,11 @@ LabeledJars = provider(
"values": "The preorder depset of label and jars.",
},
)

SemanticdbInfo = provider(
fields = {
"semanticdb_enabled": "boolean",
"target_root": "directory containing the semanticdb files (relative to execroot).",
"is_bundled_in_jar": "boolean: whether the semanticdb files are bundled inside the jar",
},
)
2 changes: 1 addition & 1 deletion rules/scala/workspace.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def scala_repositories():
["bazel_skylib", None, "https://github.com/bazelbuild/bazel-skylib/releases/download/{version}/bazel-skylib-{version}.tar.gz".format(version = skylib_tag), "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506"],
["com_google_protobuf", "protobuf-" + protobuf_tag, "https://github.com/protocolbuffers/protobuf/archive/v{}.tar.gz".format(protobuf_tag), "22fdaf641b31655d4b2297f9981fa5203b2866f8332d3c6333f6b0107bb320de"],
["rules_proto", "rules_proto-" + rules_proto_tag, "https://github.com/bazelbuild/rules_proto/archive/{}.tar.gz".format(rules_proto_tag), "dc3fb206a2cb3441b485eb1e423165b231235a1ea9b031b4433cf7bc1fa460dd"],
["rules_python", "rules_python-" + rules_python_tag, "https://github.com/bazelbuild/rules_python/releases/download/{version}/rules_python-{version}.tar.gz".format(version = rules_python_tag)],
["rules_python", "rules_python-" + rules_python_tag, "https://github.com/bazelbuild/rules_python/releases/download/{version}/rules_python-{version}.tar.gz".format(version = rules_python_tag), "ffc7b877c95413c82bfd5482c017edcf759a6250d8b24e82f41f3c8b8d9e287e"],
]
for dep in rules_deps:
maybe(http_archive, name = dep[0], strip_prefix = dep[1], url = dep[2], sha256 = dep[3] if len(dep) == 4 else "")
4 changes: 4 additions & 0 deletions scala3/private/toolchain_constants.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ SCALA_TOOLCHAIN_ATTRS = {
default = False,
doc = "Enable SemanticDB.",
),
"semanticdb_bundle_in_jar": attr.bool(
default = False,
doc = "Whether semanticdb should be generated inside the output jar file or separately.",
),
"is_zinc": attr.bool(
default = True,
doc = "Use zinc compiler?",
Expand Down
2 changes: 1 addition & 1 deletion scala3/private/worker_scala_binary.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"A module defining `worker_scala_binary` rules"

load("@bazel_skylib//lib:dicts.bzl", _dicts = "dicts")
load("//rules:providers.bzl", _ScalaConfiguration = "ScalaConfiguration")
load("//rules:jvm.bzl", _labeled_jars = "labeled_jars")
load(
"//rules:private_proxy.bzl",
Expand All @@ -17,6 +16,7 @@ load(
_phase_singlejar = "phase_singlejar",
_run_phases = "run_phases",
)
load("//rules:providers.bzl", _ScalaConfiguration = "ScalaConfiguration")

_compile_private_attributes = {
"_java_toolchain": attr.label(
Expand Down
2 changes: 1 addition & 1 deletion scala3/private/worker_scala_library.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"A module defining `worker_scala_library` rules"

load("@bazel_skylib//lib:dicts.bzl", _dicts = "dicts")
load("//rules:providers.bzl", _ScalaConfiguration = "ScalaConfiguration")
load("//rules:jvm.bzl", _labeled_jars = "labeled_jars")
load(
"//rules:private_proxy.bzl",
Expand All @@ -16,6 +15,7 @@ load(
_phase_singlejar = "phase_singlejar",
_run_phases = "run_phases",
)
load("//rules:providers.bzl", _ScalaConfiguration = "ScalaConfiguration")

_compile_private_attributes = {
"_java_toolchain": attr.label(
Expand Down
Loading

0 comments on commit 7abf6c6

Please sign in to comment.