blob: 1c318612c8e1c84c63c9ee080aec956a4cc8ff4b [file]
# Copyright 2018 The Bazel Authors. All rights reserved.
#
# 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
#
# http://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.
load("@rules_java//java:defs.bzl", "JavaInfo", "JavaPluginInfo", "java_common")
load(
"//kotlin/internal:defs.bzl",
"KtCompilerPluginOption",
"KtPluginConfiguration",
_KspPluginInfo = "KspPluginInfo",
_KtCompilerPluginInfo = "KtCompilerPluginInfo",
_KtJvmInfo = "KtJvmInfo",
_TOOLCHAIN_TYPE = "TOOLCHAIN_TYPE",
)
load(
"//kotlin/internal/jvm:compile.bzl",
_compile = "compile",
)
load(
"//kotlin/internal/utils:utils.bzl",
_utils = "utils",
)
load("//third_party:jarjar.bzl", "jarjar_action")
# borrowed from skylib to avoid adding that to the release.
def _is_absolute(path):
return path.startswith("/") or (len(path) > 2 and path[1] == ":")
def _make_providers(ctx, providers, runfiles_targets, transitive_files = depset(order = "default"), *additional_providers):
files = [ctx.outputs.jar]
if providers.java.outputs.jdeps:
files.append(providers.java.outputs.jdeps)
return [
providers.java,
providers.kt,
providers.instrumented_files,
DefaultInfo(
files = depset(files),
runfiles = ctx.runfiles(
files = ctx.files.data,
transitive_files = transitive_files,
).merge_all([
d[DefaultInfo].default_runfiles
for d in runfiles_targets
if DefaultInfo in d and d[DefaultInfo].default_runfiles
]),
),
] + list(additional_providers)
def _write_launcher_action(ctx, rjars, main_class, jvm_flags):
"""Macro that writes out a launcher script shell script.
Args:
rjars: All of the runtime jars required to launch this java target.
main_class: the main class to launch.
jvm_flags: The flags that should be passed to the jvm.
args: Args that should be passed to the Binary.
"""
jvm_flags = " ".join([ctx.expand_location(f, ctx.attr.data) for f in jvm_flags])
template = ctx.attr.java_stub_template.files.to_list()[0]
java_runtime = ctx.toolchains["@bazel_tools//tools/jdk:runtime_toolchain_type"].java_runtime
java_bin_path = java_runtime.java_executable_runfiles_path
# Following https://github.com/bazelbuild/bazel/blob/6d5b084025a26f2f6d5041f7a9e8d302c590bc80/src/main/starlark/builtins_bzl/bazel/java/bazel_java_binary.bzl#L66-L67
# Enable the security manager past deprecation.
# On bazel 6, this check isn't possible...
if getattr(java_runtime, "version", 0) >= 17:
jvm_flags = jvm_flags + " -Djava.security.manager=allow"
if ctx.configuration.coverage_enabled:
jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner
classpath = ctx.configuration.host_path_separator.join(
["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list() + jacocorunner.files.to_list()],
)
jacoco_metadata_file = ctx.actions.declare_file(
"%s.jacoco_metadata.txt" % ctx.attr.name,
sibling = ctx.outputs.executable,
)
ctx.actions.write(jacoco_metadata_file, "\n".join([
jar.short_path.replace("../", "external/")
for jar in rjars.to_list()
]))
ctx.actions.expand_template(
template = template,
output = ctx.outputs.executable,
substitutions = {
"%classpath%": classpath,
"%runfiles_manifest_only%": "",
"%java_start_class%": "com.google.testing.coverage.JacocoCoverageRunner",
"%javabin%": "JAVABIN=" + java_bin_path,
"%jvm_flags%": jvm_flags,
"%set_jacoco_metadata%": "export JACOCO_METADATA_JAR=\"$JAVA_RUNFILES/{}/{}\"".format(ctx.workspace_name, jacoco_metadata_file.short_path),
"%set_jacoco_main_class%": """export JACOCO_MAIN_CLASS={}""".format(main_class),
"%set_jacoco_java_runfiles_root%": """export JACOCO_JAVA_RUNFILES_ROOT=$JAVA_RUNFILES/{}/""".format(ctx.workspace_name),
"%set_java_coverage_new_implementation%": """export JAVA_COVERAGE_NEW_IMPLEMENTATION=YES""",
"%workspace_prefix%": ctx.workspace_name + "/",
"%test_runtime_classpath_file%": "export TEST_RUNTIME_CLASSPATH_FILE=${JAVA_RUNFILES}",
"%needs_runfiles%": "0" if _is_absolute(java_bin_path) else "1",
},
is_executable = True,
)
return [jacoco_metadata_file]
classpath = ctx.configuration.host_path_separator.join(
["${RUNPATH}%s" % (j.short_path) for j in rjars.to_list()],
)
ctx.actions.expand_template(
template = template,
output = ctx.outputs.executable,
substitutions = {
"%classpath%": classpath,
"%runfiles_manifest_only%": "",
"%java_start_class%": main_class,
"%javabin%": "JAVABIN=" + java_bin_path,
"%jvm_flags%": jvm_flags,
"%set_jacoco_metadata%": "",
"%set_jacoco_main_class%": "",
"%set_jacoco_java_runfiles_root%": "",
"%set_java_coverage_new_implementation%": """export JAVA_COVERAGE_NEW_IMPLEMENTATION=NO""",
"%workspace_prefix%": ctx.workspace_name + "/",
"%test_runtime_classpath_file%": "export TEST_RUNTIME_CLASSPATH_FILE=${JAVA_RUNFILES}",
"%needs_runfiles%": "0" if _is_absolute(java_bin_path) else "1",
},
is_executable = True,
)
return []
# buildifier: disable=unused-variable
def _is_source_jar_stub(jar):
"""Workaround for intellij plugin expecting a source jar"""
return jar.path.endswith("third_party/empty.jar")
def _unify_jars(ctx):
if bool(ctx.attr.jar):
return struct(class_jar = ctx.file.jar, source_jar = ctx.file.srcjar, ijar = None)
else:
# Legacy handling.
jars = []
source_jars = []
if (ctx.file.srcjar and not ("%s" % ctx.file.srcjar.path).endswith("third_party/empty.jar")):
source_jars.append(ctx.file.srcjar)
# There must be a single jar jar and it can either be a filegroup or a JavaInfo.
for jar in ctx.attr.jars:
# If a JavaInfo is available it's because it was picked up from a `maven_jar` style attribute -- e.g.,
# @com_google_guava_guava//jar. so the transitive_compile_jars or the transitive_runtime_jars should not be
# visited -- descending into these results in ijars entering the graph.
if JavaInfo in jar:
jars += jar[JavaInfo].full_compile_jars.to_list()
source_jars += jar[JavaInfo].transitive_source_jars.to_list()
else:
# this branch occurs when the attr was a filegroup.
for file in jar.files.to_list():
if file.basename.endswith("-sources.jar"):
source_jars.append(file)
elif file.basename.endswith(".jar"):
jars.append(file)
else:
fail("a jar pointing to a filegroup must either end with -sources.jar or .jar: %s", file)
if len(jars) > 1:
fail("Got more than one jar, this is an error create an issue: %s" % jars)
if len(source_jars) > 1:
fail("Got more than one source jar. " +
"Did you include both srcjar and a sources jar in the jars attribute?: %s" % source_jars)
return struct(class_jar = jars[0], source_jar = source_jars[0] if len(source_jars) == 1 else None, ijar = None)
def kt_jvm_import_impl(ctx):
if bool(ctx.attr.jars) and bool(ctx.attr.jar):
fail("Cannot use both jars= and jar= attribute. Prefer jar=")
artifact = _unify_jars(ctx)
kt_info = _KtJvmInfo(
module_name = _utils.derive_module_name(ctx),
module_jars = [],
exported_compiler_plugins = depset(getattr(ctx.attr, "exported_compiler_plugins", [])),
outputs = struct(
jars = [artifact],
),
)
return [
DefaultInfo(
files = depset(direct = [artifact.class_jar]),
runfiles = ctx.runfiles(
# Append class jar with the optional sources jar
files = [artifact.class_jar] + [artifact.source_jar] if artifact.source_jar else [],
).merge_all([d[DefaultInfo].default_runfiles for d in ctx.attr.deps]),
),
JavaInfo(
output_jar = artifact.class_jar,
compile_jar = artifact.class_jar,
source_jar = artifact.source_jar,
runtime_deps = [dep[JavaInfo] for dep in ctx.attr.runtime_deps if JavaInfo in dep],
deps = [dep[JavaInfo] for dep in ctx.attr.deps if JavaInfo in dep],
exports = [d[JavaInfo] for d in getattr(ctx.attr, "exports", [])],
neverlink = getattr(ctx.attr, "neverlink", False),
),
kt_info,
]
def kt_jvm_library_impl(ctx):
if ctx.attr.neverlink and ctx.attr.runtime_deps:
fail("runtime_deps and neverlink is nonsensical.", attr = "runtime_deps")
if not ctx.attr.srcs and len(ctx.attr.deps) > 0:
fail(
"deps without srcs is invalid." +
"\nTo add runtime dependencies use runtime_deps." +
"\nTo export libraries use exports.",
attr = "deps",
)
return _make_providers(
ctx,
_compile.kt_jvm_produce_jar_actions(ctx, "kt_jvm_library") if ctx.attr.srcs or ctx.attr.resources else _compile.export_only_providers(
ctx = ctx,
actions = ctx.actions,
outputs = ctx.outputs,
attr = ctx.attr,
),
runfiles_targets = ctx.attr.deps + ctx.attr.exports,
)
def kt_jvm_binary_impl(ctx):
providers = _compile.kt_jvm_produce_jar_actions(ctx, "kt_jvm_binary")
jvm_flags = []
if hasattr(ctx.fragments.java, "default_jvm_opts"):
jvm_flags = ctx.fragments.java.default_jvm_opts
jvm_flags.extend(ctx.attr.jvm_flags)
_write_launcher_action(
ctx,
providers.java.transitive_runtime_jars,
ctx.attr.main_class,
jvm_flags,
)
if len(ctx.attr.srcs) == 0 and len(ctx.attr.deps) > 0:
fail("deps without srcs is invalid. To add runtime classpath and resources, use runtime_deps.", attr = "deps")
return _make_providers(
ctx,
providers,
runfiles_targets = ctx.attr.deps,
transitive_files = depset(
order = "default",
transitive = [providers.java.transitive_runtime_jars],
direct = ctx.files._java_runtime,
),
)
_SPLIT_STRINGS = [
"src/test/java/",
"src/test/kotlin/",
"javatests/",
"kotlin/",
"java/",
"test/",
]
def kt_jvm_junit_test_impl(ctx):
providers = _compile.kt_jvm_produce_jar_actions(ctx, "kt_jvm_test")
runtime_jars = depset(ctx.files._bazel_test_runner, transitive = [providers.java.transitive_runtime_jars])
coverage_runfiles = []
if ctx.configuration.coverage_enabled:
jacocorunner = ctx.toolchains[_TOOLCHAIN_TYPE].jacocorunner
coverage_runfiles = jacocorunner.files.to_list()
test_class = ctx.attr.test_class
# If no test_class, do a best-effort attempt to infer one.
if not bool(ctx.attr.test_class):
for file in ctx.files.srcs:
package_relative_path = file.path.replace(ctx.label.package + "/", "")
if package_relative_path.split(".")[0] == ctx.attr.name:
for splitter in _SPLIT_STRINGS:
elements = file.short_path.split(splitter, 1)
if len(elements) == 2:
test_class = elements[1].split(".")[0].replace("/", ".")
break
jvm_flags = []
if hasattr(ctx.fragments.java, "default_jvm_opts"):
jvm_flags = ctx.fragments.java.default_jvm_opts
jvm_flags.extend(ctx.attr.jvm_flags)
coverage_metadata = _write_launcher_action(
ctx,
runtime_jars,
main_class = ctx.attr.main_class,
jvm_flags = [
"-ea",
"-Dbazel.test_suite=%s" % test_class,
] + jvm_flags,
)
return _make_providers(
ctx,
providers,
ctx.attr.deps,
depset(
order = "default",
transitive = [runtime_jars, depset(coverage_runfiles), depset(coverage_metadata)],
direct = ctx.files._java_runtime,
),
# adds common test variables, including TEST_WORKSPACE.
testing.TestEnvironment(environment = ctx.attr.env, inherited_environment = ctx.attr.env_inherit),
)
_KtCompilerPluginClasspathInfo = provider(
fields = {
"reshaded_infos": "list reshaded JavaInfos of a compiler library",
"infos": "list JavaInfos of a compiler library",
},
)
def kt_compiler_deps_aspect_impl(target, ctx):
"""
Collects and reshades (if necessary) all jars in the plugin transitive closure.
Args:
target: Target of the rule being inspected
ctx: aspect ctx
Returns:
list of _KtCompilerPluginClasspathInfo
"""
transitive_infos = [
t[_KtCompilerPluginClasspathInfo]
for d in ["deps", "runtime_deps", "exports"]
for t in getattr(ctx.rule.attr, d, [])
if _KtCompilerPluginClasspathInfo in t
]
reshaded_infos = []
infos = [
i
for t in transitive_infos
for i in t.infos
]
if JavaInfo in target:
ji = target[JavaInfo]
infos.append(ji)
reshaded_infos.append(
_reshade_embedded_kotlinc_jars(
target = target,
ctx = ctx,
jars = ji.runtime_output_jars,
deps = [
i
for t in transitive_infos
for i in t.reshaded_infos
],
),
)
return [
_KtCompilerPluginClasspathInfo(
reshaded_infos = reshaded_infos,
infos = [java_common.merge(infos)],
),
]
def _reshade_embedded_kotlinc_jars(target, ctx, jars, deps):
reshaded = [
jarjar_action(
actions = ctx.actions,
jarjar = ctx.executable._jarjar,
rules = ctx.file._kotlin_compiler_reshade_rules,
input = jar,
output = ctx.actions.declare_file(
"%s_reshaded_%s" % (target.label.name, jar.basename),
),
)
for jar in jars
]
# JavaInfo only takes a single jar, so create many and merge them.
return java_common.merge(
[
JavaInfo(output_jar = jar, compile_jar = jar, deps = deps)
for jar in reshaded
],
)
def _resolve_plugin_options(id, string_list_dict, expand_location):
"""
Resolves plugin options from a string dict to a dict of strings.
Args:
id: the plugin id
string_list_dict: a dict of list[string].
Returns:
a dict of strings
"""
options = []
for (k, vs) in string_list_dict.items():
for v in vs:
if "=" in k:
fail("kotlin compiler option keys cannot contain the = symbol")
value = k + "=" + expand_location(v) if v else k
options.append(KtCompilerPluginOption(id = id, value = value))
return options
# This is naive reference implementation for resolving configurations.
# A more complicated plugin will need to provide its own implementation.
def _resolve_plugin_cfg(info, options, deps, expand_location):
ji = java_common.merge([dep[JavaInfo] for dep in deps if JavaInfo in dep])
classpath = depset(ji.runtime_output_jars, transitive = [ji.transitive_runtime_jars])
return KtPluginConfiguration(
id = info.id,
options = _resolve_plugin_options(info.id, options, expand_location),
classpath = classpath,
data = depset(),
)
def kt_compiler_plugin_impl(ctx):
plugin_id = ctx.attr.id
deps = ctx.attr.deps
info = None
if ctx.attr.target_embedded_compiler:
info = java_common.merge([
i
for d in deps
for i in d[_KtCompilerPluginClasspathInfo].reshaded_infos
])
else:
info = java_common.merge([
i
for d in deps
for i in d[_KtCompilerPluginClasspathInfo].infos
])
classpath = depset(info.runtime_output_jars, transitive = [info.transitive_runtime_jars])
# TODO(1035): Migrate kt_compiler_plugin.options to string_list_dict
options = _resolve_plugin_options(plugin_id, {k: [v] for (k, v) in ctx.attr.options.items()}, ctx.expand_location)
return [
DefaultInfo(files = classpath),
_KtCompilerPluginInfo(
id = plugin_id,
classpath = classpath,
options = options,
stubs = ctx.attr.stubs_phase,
compile = ctx.attr.compile_phase,
resolve_cfg = _resolve_plugin_cfg,
),
]
def kt_plugin_cfg_impl(ctx):
plugin = ctx.attr.plugin[_KtCompilerPluginInfo]
return plugin.resolve_cfg(plugin, ctx.attr.options, ctx.attr.deps, ctx.expand_location)
def kt_ksp_plugin_impl(ctx):
deps = ctx.attr.deps
if ctx.attr.target_embedded_compiler:
info = java_common.merge([
i
for d in deps
for i in d[_KtCompilerPluginClasspathInfo].reshaded_infos
])
else:
info = java_common.merge([dep[JavaInfo] for dep in deps])
classpath = depset(info.runtime_output_jars, transitive = [info.transitive_runtime_jars])
return [
DefaultInfo(files = classpath),
_KspPluginInfo(
plugins = [
JavaPluginInfo(
runtime_deps = [
info,
],
processor_class = ctx.attr.processor_class,
# rules_kotlin doesn't support stripping non-api generating annotation
# processors out of the public ABI.
generates_api = True,
),
],
generates_java = ctx.attr.generates_java,
),
]