blob: 070c3f4ffbb206178d56e9996d43a4f77be5771b [file] [log] [blame]
load(":coverage.bzl", "coverage_baseline", "coverage_java_test")
load(":functions.bzl", "explicit_target")
load(":merge_archives.bzl", "merge_jars")
load(":lint.bzl", "lint_test")
load(":merge_archives.bzl", "run_singlejar")
load("@bazel_tools//tools/jdk:toolchain_utils.bzl", "find_java_runtime_toolchain", "find_java_toolchain")
load(":functions.bzl", "create_option_file")
def test_kotlin_use_ir():
return select({
"//tools/base/bazel:kotlin_no_use_ir": False,
"//conditions:default": True,
})
def kotlin_compile(ctx, name, srcs, deps, friend_jars, out, out_ijar, java_runtime, kotlinc_opts, transitive_classpath):
"""Runs kotlinc on the given source files.
Args:
ctx: the analysis context
name: the name of the module being compiled
srcs: a list of Java and Kotlin source files
deps: a list of JavaInfo providers from direct dependencies
friend_jars: a list of friend jars (allowing access to 'internal' members)
out: the output jar file
out_ijar: the output ijar file or None to disable ijar creation
java_runtime: a JavaRuntimeInfo provider corresponding to the target JVM
kotlinc_opts: list of additional flags to pass to the Kotlin compiler
transitive_classpath: whether to include transitive deps in the compile classpath
Returns:
JavaInfo for the resulting jar.
Expects that ctx.executable._kotlinc and ctx.file._jvm_abi_gen are defined.
Note: kotlinc only compiles Kotlin, not Java. So if there are Java
sources, then you will also need to run javac after this action.
"""
# TODO: Either disable transitive_classpath in all cases, or otherwise
# implement strict-deps enforcement for Kotlin to ensure that targets
# declare dependencies on everything they directly use.
merged_deps = java_common.merge(deps)
if transitive_classpath:
merged_deps = java_common.make_non_strict(merged_deps)
classpath = merged_deps.compile_jars
args = ctx.actions.args()
args.add("-module-name", name)
args.add("-nowarn") # Mirrors the default javac opts.
args.add("-api-version", "1.5")
args.add("-language-version", "1.5")
args.add("-Xjvm-default=all-compatibility")
args.add("-no-stdlib")
args.add("-Xsam-conversions=class") # Needed for Gradle configuration caching (see b/202512551).
tools = []
if out_ijar:
tools.append(ctx.file._jvm_abi_gen)
args.add(ctx.file._jvm_abi_gen, format = "-Xplugin=%s")
args.add("-P", out_ijar, format = "plugin:org.jetbrains.kotlin.jvm.abi:outputDir=%s")
# Dependency jars may be compiled with a new kotlinc IR backend.
args.add("-Xallow-unstable-dependencies")
# Use the Compiler Compose plugin
if ctx.attr.kotlin_use_compose:
tools.append(ctx.file._compose_plugin)
args.add(ctx.file._compose_plugin, format = "-Xplugin=%s")
args.add("-P", "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true")
# Add "use-ir" to enable the new IR backend for kotlinc tasks when the
# attribute "kotlin_use_ir" is set
# Using the Compose Plugin implies the use of the IR backend
if ctx.attr.kotlin_use_ir or ctx.attr.kotlin_use_compose:
args.add("-Xuse-ir")
# Use custom JRE instead of the default one picked by kotlinc.
args.add("-jdk-home", java_runtime.java_home)
# Note: there are some open questions regarding the transitivity of friends.
# See https://github.com/bazelbuild/rules_kotlin/issues/211.
args.add_joined(friend_jars, join_with = ",", format_joined = "-Xfriend-paths=%s")
args.add_joined("-cp", classpath, join_with = ctx.configuration.host_path_separator)
args.add("-d", out)
args.add_all(srcs)
# Add custom kotlinc options last so that they override the global options.
args.add_all(kotlinc_opts)
# To enable persistent Bazel workers, all arguments must come in an argfile.
args.use_param_file("@%s", use_always = True)
args.set_param_file_format("multiline")
ctx.actions.run(
inputs = depset(direct = srcs, transitive = [classpath, java_runtime.files]),
outputs = [out, out_ijar] if out_ijar else [out],
tools = tools,
mnemonic = "kotlinc",
arguments = [args],
executable = ctx.executable._kotlinc,
execution_requirements = {"supports-multiplex-workers": "1"},
)
return JavaInfo(output_jar = out, compile_jar = out_ijar or out)
def kotlin_test(
name,
srcs,
deps = [],
runtime_deps = [],
friends = [],
kotlinc_opts = [],
visibility = None,
lint_baseline = None,
lint_classpath = [],
**kwargs):
kotlin_library(
name = name + ".testlib",
srcs = srcs,
deps = deps,
testonly = True,
runtime_deps = runtime_deps,
jar = name + ".jar",
lint_baseline = lint_baseline,
lint_classpath = lint_classpath,
lint_is_test_sources = True,
visibility = visibility,
friends = friends,
kotlinc_opts = kotlinc_opts,
)
coverage_java_test(
name = name + ".test",
runtime_deps = [
":" + name + ".testlib",
] + runtime_deps,
visibility = visibility,
**kwargs
)
native.test_suite(
name = name,
tests = [name + ".test"],
)
# Creates actions to generate the sources jar
def _sources(ctx, srcs, source_jars, jar, java_toolchain):
java_common.pack_sources(
ctx.actions,
output_source_jar = jar,
sources = srcs,
source_jars = source_jars,
java_toolchain = java_toolchain,
)
# Creates actions to generate a resources_jar from the given resources.
def _resources(ctx, resources, notice, resources_jar):
prefix = ctx.attr.resource_strip_prefix
rel_paths = []
if notice:
rel_paths.append((notice.basename, notice))
for res in resources:
short = res.short_path
if short.startswith(prefix):
short = short[len(prefix):]
if short.startswith("/"):
short = short[1:]
rel_paths.append((short, res))
zipper_args = ["cC" if ctx.attr.compress_resources else "c", resources_jar.path]
zipper_files = "".join([k + "=" + v.path + "\n" for k, v in rel_paths])
zipper_list = create_option_file(ctx, resources_jar.basename + ".res.lst", zipper_files)
zipper_args += ["@" + zipper_list.path]
ctx.actions.run(
inputs = resources + ([notice] if notice else []) + [zipper_list],
outputs = [resources_jar],
executable = ctx.executable._zipper,
arguments = zipper_args,
progress_message = "Creating resources %s (%d files)" % (resources_jar.short_path, len(resources)),
mnemonic = "zipper",
)
def kotlin_library(
name,
srcs,
deps = None,
javacopts = [],
kotlinc_opts = [],
lint_baseline = None,
lint_classpath = [],
lint_is_test_sources = False,
lint_timeout = None,
compress_resources = False,
testonly = False,
stdlib = "@maven//:org.jetbrains.kotlin.kotlin-stdlib",
kotlin_use_compose = False,
**kwargs):
"""Compiles a library jar from Java and Kotlin sources
Args:
srcs: The sources of the library.
javacopts: Additional javac options.
resources: Resources to add to the jar.
resources_strip_prefix: The prefix to strip from the resources path.
deps: The dependencies of this library.
runtime_deps: The runtime dependencies.
friends: The list of kotlin-friends.
compress_resources: Whether to compress resources.
notice: An optional notice file to be included in the jar.
coordinates: The maven coordinates of this artifact.
exclusions: Files to exclude from the generated pom file.
lint_*: Lint configuration arguments
module_name: The kotlin module name.
"""
kotlins = [src for src in srcs if src.endswith(".kt")]
javas = [src for src in srcs if src.endswith(".java")]
source_jars = [src for src in srcs if src.endswith(".srcjar")]
# Target 11 explicitly to avoid JDK updates impacting generated bytecode (b/166472930).
javacopts = javacopts + ["--release", "11"]
kotlinc_opts = kotlinc_opts + ["-jvm-target", "11"]
# Include non-test kotlin libraries in coverage
if not testonly:
coverage_baseline(
name = name,
srcs = javas + kotlins,
)
jar = kwargs.pop("jar", "lib" + name + ".jar")
_kotlin_library(
name = name,
jar = jar,
java_srcs = javas,
deps = deps,
kotlin_srcs = kotlins,
source_jars = source_jars,
compress_resources = compress_resources,
kotlin_use_compose = kotlin_use_compose,
kotlin_use_ir = test_kotlin_use_ir(),
javacopts = javacopts,
kotlinc_opts = kotlinc_opts,
testonly = testonly,
stdlib = stdlib,
**kwargs
)
# TODO move lint tests out of here
if lint_baseline:
# TODO: use srcs once the migration is completed
lint_srcs = javas + kotlins
if not lint_srcs:
fail("lint_baseline set for rule that has no sources")
lint_test(
name = name + "_lint_test",
srcs = lint_srcs,
baseline = lint_baseline,
deps = deps + lint_classpath,
custom_rules = ["//tools/base/lint:studio-checks.lint-rules.jar"],
tags = ["no_windows"],
is_test_sources = lint_is_test_sources,
timeout = lint_timeout if lint_timeout else None,
)
def _kotlin_library_impl(ctx):
java_srcs = ctx.files.java_srcs
kotlin_srcs = ctx.files.kotlin_srcs
source_jars = ctx.files.source_jars
name = ctx.label.name
use_ijar = not ctx.attr.testonly
java_jar = ctx.actions.declare_file(name + ".java.jar") if java_srcs or source_jars else None
kotlin_jar = ctx.actions.declare_file(name + ".kotlin.jar") if kotlin_srcs else None
kotlin_ijar = ctx.actions.declare_file(name + ".kotlin-ijar.jar") if kotlin_srcs and use_ijar else None
full_ijar = ctx.actions.declare_file(name + ".merged-ijar-kotlin-lib.jar") if use_ijar else None
deps = [dep[JavaInfo] for dep in ctx.attr.deps + ctx.attr.exports]
java_info_deps = [dep[JavaInfo] for dep in ctx.attr.deps]
# Kotlin
jars = []
ijars = []
kotlin_providers = []
if kotlin_srcs:
if ctx.attr.stdlib:
deps.append(ctx.attr.stdlib[JavaInfo])
java_info_deps.append(ctx.attr.stdlib[JavaInfo])
friend_jars = []
for friend in ctx.attr.friends:
friend_jars += friend[JavaInfo].compile_jars.to_list()
kotlin_providers += [kotlin_compile(
ctx = ctx,
name = ctx.attr.module_name,
srcs = java_srcs + kotlin_srcs,
deps = deps,
friend_jars = friend_jars,
out = kotlin_jar,
out_ijar = kotlin_ijar,
java_runtime = ctx.attr._kt_java_runtime[java_common.JavaRuntimeInfo],
kotlinc_opts = ctx.attr.kotlinc_opts,
transitive_classpath = True, # Matches Java rules (sans strict-deps enforcement)
)]
jars += [kotlin_jar]
if use_ijar:
ijars += [kotlin_ijar]
# Resources.
# We only add Resources to the output jar, but exclude them from the compile_jar.
if ctx.files.resources or ctx.files.notice:
resources_jar = ctx.actions.declare_file(name + ".res.jar")
_resources(ctx, ctx.files.resources, ctx.file.notice, resources_jar)
jars += [resources_jar]
java_toolchain = find_java_toolchain(ctx, ctx.attr._java_toolchain)
# Java
if java_srcs or source_jars:
java_jar = ctx.actions.declare_file(name + ".java.jar")
java_provider = java_common.compile(
ctx,
source_files = java_srcs,
source_jars = source_jars,
output = java_jar,
deps = deps + kotlin_providers,
javac_opts = java_common.default_javac_opts(java_toolchain = java_toolchain) + ctx.attr.javacopts,
java_toolchain = java_toolchain,
plugins = [plugin[JavaPluginInfo] for plugin in ctx.attr.plugins],
# TODO(b/216385876) After updating to Bazel 5.0, use enable_compile_jar_action = use_ijar,
)
jars += [java_jar]
if use_ijar:
ijars += java_provider.compile_jars.to_list()
run_singlejar(
ctx = ctx,
jars = jars,
out = ctx.outputs.jar,
# allow_duplicates = True, # TODO: Ideally we could be more strict here.
)
if use_ijar:
run_singlejar(
ctx = ctx,
jars = ijars,
out = full_ijar,
# Even if there are no duplicates in the jars, there might be duplicates in the ijars.
# For example, protoc adds a stripped version of its runtime dependency under
# META-INF/TRANSITIVE, which results in duplicates for multiple proto dependencies.
allow_duplicates = True,
)
_sources(ctx, java_srcs + kotlin_srcs, source_jars, ctx.outputs.source_jar, java_toolchain)
java_info = JavaInfo(
output_jar = ctx.outputs.jar,
compile_jar = full_ijar or ctx.outputs.jar,
source_jar = ctx.outputs.source_jar,
deps = java_info_deps,
exports = [dep[JavaInfo] for dep in ctx.attr.exports],
runtime_deps = java_info_deps,
)
transitive_runfiles = depset(transitive = [
dep[DefaultInfo].default_runfiles.files
for dep in ctx.attr.deps + ctx.attr.exports
if dep[DefaultInfo].default_runfiles
])
runfiles = ctx.runfiles(files = ctx.files.data, transitive_files = transitive_runfiles)
return [
java_info,
DefaultInfo(files = depset([ctx.outputs.jar]), runfiles = runfiles),
]
_kotlin_library = rule(
attrs = {
"java_srcs": attr.label_list(allow_files = True),
"kotlin_srcs": attr.label_list(allow_files = True),
"source_jars": attr.label_list(allow_files = True),
"resources": attr.label_list(allow_files = True),
"notice": attr.label(allow_single_file = True),
"data": attr.label_list(allow_files = True),
"friends": attr.label_list(),
"jar": attr.output(mandatory = True),
"deps": attr.label_list(providers = [JavaInfo]),
"exports": attr.label_list(providers = [JavaInfo]),
"runtime_deps": attr.label_list(
providers = [JavaInfo],
),
"module_name": attr.string(
default = "unnamed",
),
"resource_strip_prefix": attr.string(),
"javacopts": attr.string_list(),
"kotlinc_opts": attr.string_list(),
"kotlin_use_compose": attr.bool(),
"kotlin_use_ir": attr.bool(),
"compress_resources": attr.bool(),
"plugins": attr.label_list(
providers = [JavaPluginInfo],
),
"stdlib": attr.label(),
"_java_toolchain": attr.label(default = Label("@bazel_tools//tools/jdk:current_java_toolchain")),
"_kt_java_runtime": attr.label(
# We need this to be able to target JRE 11 in Kotlin, because
# Kotlinc does not support the --release 11 Javac option.
default = Label("//prebuilts/studio/jdk:jdk11_runtime"),
providers = [java_common.JavaRuntimeInfo],
cfg = "exec",
),
"_kotlinc": attr.label(
executable = True,
cfg = "host",
default = Label("//tools/base/bazel:kotlinc"),
),
"_jvm_abi_gen": attr.label(
default = Label("//prebuilts/tools/common/m2:jvm-abi-gen-plugin"),
cfg = "host",
allow_single_file = [".jar"],
),
"_zipper": attr.label(
default = Label("@bazel_tools//tools/zip:zipper"),
cfg = "host",
executable = True,
),
"_singlejar": attr.label(
default = Label("@bazel_tools//tools/jdk:singlejar"),
cfg = "host",
executable = True,
),
},
fragments = ["java"],
outputs = {
"source_jar": "%{name}-src.jar",
},
implementation = _kotlin_library_impl,
)