blob: d59fd6217bfd760ae3e4a96451cb724bc11a1757 [file] [log] [blame]
# Copyright 2023 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.
"""
Definition of java_toolchain rule and JavaToolchainInfo provider.
"""
load("//java/common:java_semantics.bzl", "semantics")
load("//java/common/rules/impl:java_helper.bzl", "helper")
load(":java_package_configuration.bzl", "JavaPackageConfigurationInfo")
load(":java_runtime.bzl", "JavaRuntimeInfo")
# copybara: default visibility
_java_common_internal = java_common.internal_DO_NOT_USE()
ToolchainInfo = platform_common.ToolchainInfo
BootClassPathInfo = java_common.BootClassPathInfo
JavaPluginDataInfo = _java_common_internal.JavaPluginDataInfo
def _java_toolchain_info_init(**_kwargs):
fail("JavaToolchainInfo instantiation is a private API")
_PRIVATE_API_DOC_STRING = "internal API, DO NOT USE!"
JavaToolchainInfo, _new_javatoolchaininfo = provider(
doc = "Information about the JDK used by the <code>java_*</code> rules.",
fields = {
"bootclasspath": "(depset[File]) The Java target bootclasspath entries. Corresponds to javac's -bootclasspath flag.",
"ijar": "(FilesToRunProvider) The ijar executable.",
"jacocorunner": "(FilesToRunProvider) The jacocorunner used by the toolchain.",
"java_runtime": "(JavaRuntimeInfo) The java runtime information.",
"jvm_opt": "(depset[str]) The default options for the JVM running the java compiler and associated tools.",
"label": "(label) The toolchain label.",
"proguard_allowlister": "(FilesToRunProvider) The binary to validate proguard configuration.",
"single_jar": "(FilesToRunProvider) The SingleJar deploy jar.",
"source_version": "(str) The java source version.",
"target_version": "(str) The java target version.",
"tools": "(depset[File]) The compilation tools.",
# private
"_android_linter": _PRIVATE_API_DOC_STRING,
"_bootclasspath_info": _PRIVATE_API_DOC_STRING,
"_bytecode_optimizer": _PRIVATE_API_DOC_STRING,
"_compatible_javacopts": _PRIVATE_API_DOC_STRING,
"_deps_checker": _PRIVATE_API_DOC_STRING,
"_forcibly_disable_header_compilation": _PRIVATE_API_DOC_STRING,
"_gen_class": _PRIVATE_API_DOC_STRING,
"_header_compiler": _PRIVATE_API_DOC_STRING,
"_header_compiler_builtin_processors": _PRIVATE_API_DOC_STRING,
"_header_compiler_direct": _PRIVATE_API_DOC_STRING,
"_javabuilder": _PRIVATE_API_DOC_STRING,
"_javacopts": _PRIVATE_API_DOC_STRING,
"_javacopts_list": _PRIVATE_API_DOC_STRING,
"_javac_supports_workers": _PRIVATE_API_DOC_STRING,
"_javac_supports_multiplex_workers": _PRIVATE_API_DOC_STRING,
"_javac_supports_worker_cancellation": _PRIVATE_API_DOC_STRING,
"_javac_supports_worker_multiplex_sandboxing": _PRIVATE_API_DOC_STRING,
"_jspecify_info": _PRIVATE_API_DOC_STRING,
"_local_java_optimization_config": _PRIVATE_API_DOC_STRING,
"_one_version_tool": _PRIVATE_API_DOC_STRING,
"_one_version_allowlist": _PRIVATE_API_DOC_STRING,
"_one_version_allowlist_for_tests": _PRIVATE_API_DOC_STRING,
"_package_configuration": _PRIVATE_API_DOC_STRING,
"_reduced_classpath_incompatible_processors": _PRIVATE_API_DOC_STRING,
"_timezone_data": _PRIVATE_API_DOC_STRING,
},
init = _java_toolchain_info_init,
)
def _java_toolchain_impl(ctx):
javac_opts_list = _get_javac_opts(ctx)
bootclasspath_info = _get_bootclasspath_info(ctx)
java_runtime = _get_java_runtime(ctx)
if java_runtime and java_runtime.lib_ct_sym:
header_compiler_direct_data = [java_runtime.lib_ct_sym]
header_compiler_direct_jvm_opts = ["-Dturbine.ctSymPath=" + java_runtime.lib_ct_sym.path]
elif java_runtime and java_runtime.java_home:
# Turbine finds ct.sym relative to java.home.
header_compiler_direct_data = []
header_compiler_direct_jvm_opts = ["-Djava.home=" + java_runtime.java_home]
else:
header_compiler_direct_data = []
header_compiler_direct_jvm_opts = []
if ctx.attr.oneversion_allowlist and ctx.attr.oneversion_whitelist:
fail("oneversion_allowlist and oneversion_whitelist are mutually exclusive")
oneversion_allowlist = ctx.file.oneversion_allowlist if ctx.file.oneversion_allowlist else ctx.file.oneversion_whitelist
java_toolchain_info = _new_javatoolchaininfo(
bootclasspath = bootclasspath_info.bootclasspath,
ijar = ctx.attr.ijar.files_to_run if ctx.attr.ijar else None,
jacocorunner = ctx.attr.jacocorunner.files_to_run if ctx.attr.jacocorunner else None,
java_runtime = java_runtime,
jvm_opt = depset(_java_common_internal.expand_java_opts(ctx, "jvm_opts", tokenize = False, exec_paths = True)),
label = ctx.label,
proguard_allowlister = ctx.attr.proguard_allowlister.files_to_run if ctx.attr.proguard_allowlister else None,
single_jar = ctx.attr.singlejar.files_to_run,
source_version = ctx.attr.source_version,
target_version = ctx.attr.target_version,
tools = depset(ctx.files.tools),
# private
_android_linter = _get_android_lint_tool(ctx),
_bootclasspath_info = bootclasspath_info,
_bytecode_optimizer = _get_tool_from_executable(ctx, "_bytecode_optimizer"),
_compatible_javacopts = _get_compatible_javacopts(ctx),
_deps_checker = ctx.file.deps_checker,
_forcibly_disable_header_compilation = ctx.attr.forcibly_disable_header_compilation,
_gen_class = ctx.file.genclass,
_header_compiler = _get_tool_from_ctx(ctx, "header_compiler", "turbine_data", "turbine_jvm_opts"),
_header_compiler_builtin_processors = depset(ctx.attr.header_compiler_builtin_processors),
_header_compiler_direct = _get_tool_from_executable(
ctx,
"header_compiler_direct",
data = header_compiler_direct_data,
jvm_opts = header_compiler_direct_jvm_opts,
),
_javabuilder = _get_tool_from_ctx(ctx, "javabuilder", "javabuilder_data", "javabuilder_jvm_opts"),
_javacopts = helper.detokenize_javacopts(javac_opts_list),
_javacopts_list = javac_opts_list,
_javac_supports_workers = ctx.attr.javac_supports_workers,
_javac_supports_multiplex_workers = ctx.attr.javac_supports_multiplex_workers,
_javac_supports_worker_cancellation = ctx.attr.javac_supports_worker_cancellation,
_javac_supports_worker_multiplex_sandboxing = ctx.attr.javac_supports_worker_multiplex_sandboxing,
_jspecify_info = _get_jspecify_info(ctx),
_local_java_optimization_config = ctx.files._local_java_optimization_configuration,
_one_version_tool = ctx.attr.oneversion.files_to_run if ctx.attr.oneversion else None,
_one_version_allowlist = oneversion_allowlist,
_one_version_allowlist_for_tests = ctx.file.oneversion_allowlist_for_tests,
_package_configuration = [dep[JavaPackageConfigurationInfo] for dep in ctx.attr.package_configuration],
_reduced_classpath_incompatible_processors = depset(ctx.attr.reduced_classpath_incompatible_processors, order = "preorder"),
_timezone_data = ctx.file.timezone_data,
)
toolchain_info = ToolchainInfo(java = java_toolchain_info)
return [java_toolchain_info, toolchain_info, DefaultInfo()]
def _get_bootclasspath_info(ctx):
bootclasspath_infos = [dep[BootClassPathInfo] for dep in ctx.attr.bootclasspath if BootClassPathInfo in dep]
if bootclasspath_infos:
if len(bootclasspath_infos) != 1:
fail("in attribute 'bootclasspath': expected exactly one entry with a BootClassPathInfo provider")
else:
return bootclasspath_infos[0]
else:
return BootClassPathInfo(bootclasspath = ctx.files.bootclasspath)
def _get_java_runtime(ctx):
if not ctx.attr.java_runtime:
return None
return ctx.attr.java_runtime[ToolchainInfo].java_runtime
def _get_javac_opts(ctx):
opts = []
if ctx.attr.source_version:
opts.extend(["-source", ctx.attr.source_version])
if ctx.attr.target_version:
opts.extend(["-target", ctx.attr.target_version])
if ctx.attr.xlint:
opts.append("-Xlint:" + ",".join(ctx.attr.xlint))
opts.extend(_java_common_internal.expand_java_opts(ctx, "misc", tokenize = True))
opts.extend(_java_common_internal.expand_java_opts(ctx, "javacopts", tokenize = True))
return opts
def _get_android_lint_tool(ctx):
if not ctx.attr.android_lint_runner:
return None
files_to_run = ctx.attr.android_lint_runner.files_to_run
if not files_to_run or not files_to_run.executable:
fail(ctx.attr.android_lint_runner.label, "does not refer to a valid executable target")
return struct(
tool = files_to_run,
data = depset(ctx.files.android_lint_data),
jvm_opts = depset([ctx.expand_location(opt, ctx.attr.android_lint_data) for opt in ctx.attr.android_lint_jvm_opts]),
lint_opts = [ctx.expand_location(opt, ctx.attr.android_lint_data) for opt in ctx.attr.android_lint_opts],
package_config = [dep[JavaPackageConfigurationInfo] for dep in ctx.attr.android_lint_package_configuration],
)
def _get_tool_from_ctx(ctx, tool_attr, data_attr, opts_attr):
dep = getattr(ctx.attr, tool_attr)
if not dep:
return None
files_to_run = dep.files_to_run
if not files_to_run or not files_to_run.executable:
fail(dep.label, "does not refer to a valid executable target")
data = getattr(ctx.attr, data_attr)
return struct(
tool = files_to_run,
data = depset(getattr(ctx.files, data_attr)),
jvm_opts = depset([ctx.expand_location(opt, data) for opt in getattr(ctx.attr, opts_attr)]),
)
def _get_tool_from_executable(ctx, attr_name, data = [], jvm_opts = []):
dep = getattr(ctx.attr, attr_name)
if not dep:
return None
files_to_run = dep.files_to_run
if not files_to_run or not files_to_run.executable:
fail(dep.label, "does not refer to a valid executable target")
return struct(tool = files_to_run, data = depset(data), jvm_opts = depset(jvm_opts))
def _get_compatible_javacopts(ctx):
result = {}
for key, opt_list in ctx.attr.compatible_javacopts.items():
result[key] = helper.detokenize_javacopts([token for opt in opt_list for token in ctx.tokenize(opt)])
return result
def _get_jspecify_info(ctx):
if not ctx.attr.jspecify_processor_class:
return None
stubs = ctx.files.jspecify_stubs
javacopts = []
javacopts.extend(ctx.attr.jspecify_javacopts)
if stubs:
javacopts.append("-Astubs=" + ":".join([file.path for file in stubs]))
return struct(
processor = JavaPluginDataInfo(
processor_classes = depset([ctx.attr.jspecify_processor_class]),
processor_jars = depset([ctx.file.jspecify_processor]),
processor_data = depset(stubs),
),
implicit_deps = depset([ctx.file.jspecify_implicit_deps]),
javacopts = javacopts,
packages = [target[PackageSpecificationInfo] for target in ctx.attr.jspecify_packages],
)
def _extract_singleton_list_value(dict, key):
if key in dict and type(dict[key]) == type([]):
list = dict[key]
if len(list) > 1:
fail("expected a single value for:", key, "got: ", list)
elif len(list) == 1:
dict[key] = dict[key][0]
else:
dict[key] = None
_LEGACY_ANY_TYPE_ATTRS = [
"genclass",
"deps_checker",
"header_compiler",
"header_compiler_direct",
"ijar",
"javabuilder",
"singlejar",
]
def _java_toolchain_initializer(**kwargs):
# these attributes are defined as executable `label_list`s in native but are
# expected to be singleton values. Since this is not supported in Starlark,
# we just inline the value from the list (if present) before invoking the
# rule.
for attr in _LEGACY_ANY_TYPE_ATTRS:
_extract_singleton_list_value(kwargs, attr)
return kwargs
java_toolchain = rule(
implementation = _java_toolchain_impl,
initializer = _java_toolchain_initializer,
doc = """
<p>
Specifies the configuration for the Java compiler. Which toolchain to be used can be changed through
the --java_toolchain argument. Normally you should not write those kind of rules unless you want to
tune your Java compiler.
</p>
<h4>Examples</h4>
<p>A simple example would be:
</p>
<pre class="code">
<code class="lang-starlark">
java_toolchain(
name = "toolchain",
source_version = "7",
target_version = "7",
bootclasspath = ["//tools/jdk:bootclasspath"],
xlint = [ "classfile", "divzero", "empty", "options", "path" ],
javacopts = [ "-g" ],
javabuilder = ":JavaBuilder_deploy.jar",
)
</code>
</pre>
""",
# buildifier: disable=attr-licenses
attrs = {
"android_lint_data": attr.label_list(
cfg = "exec",
allow_files = True,
doc = """
Labels of tools available for label-expansion in android_lint_jvm_opts.
""",
),
"android_lint_opts": attr.string_list(
default = [],
doc = """
The list of Android Lint arguments.
""",
),
"android_lint_jvm_opts": attr.string_list(
default = [],
doc = """
The list of arguments for the JVM when invoking Android Lint.
""",
),
"android_lint_package_configuration": attr.label_list(
cfg = "exec",
providers = [JavaPackageConfigurationInfo],
allow_files = True,
doc = """
Android Lint Configuration that should be applied to the specified package groups.
""",
),
"android_lint_runner": attr.label(
cfg = "exec",
executable = True,
allow_single_file = True,
doc = """
Label of the Android Lint runner, if any.
""",
),
"bootclasspath": attr.label_list(
default = [],
allow_files = True,
doc = """
The Java target bootclasspath entries. Corresponds to javac's -bootclasspath flag.
""",
),
"compatible_javacopts": attr.string_list_dict(
doc = """Internal API, do not use!""",
),
"deps_checker": attr.label(
allow_single_file = True,
cfg = "exec",
executable = True,
doc = """
Label of the ImportDepsChecker deploy jar.
""",
),
"forcibly_disable_header_compilation": attr.bool(
default = False,
doc = """
Overrides --java_header_compilation to disable header compilation on platforms that do not
support it, e.g. JDK 7 Bazel.
""",
),
"genclass": attr.label(
allow_single_file = True,
cfg = "exec",
executable = True,
doc = """
Label of the GenClass deploy jar.
""",
),
"header_compiler": attr.label(
allow_single_file = True,
cfg = "exec",
executable = True,
doc = """
Label of the header compiler. Required if --java_header_compilation is enabled.
""",
),
"header_compiler_direct": attr.label(
allow_single_file = True,
cfg = "exec",
executable = True,
doc = """
Optional label of the header compiler to use for direct classpath actions that do not
include any API-generating annotation processors.
<p>This tool does not support annotation processing.
""",
),
"header_compiler_builtin_processors": attr.string_list(
doc = """Internal API, do not use!""",
),
"ijar": attr.label(
cfg = "exec",
allow_files = True,
executable = True,
doc = """
Label of the ijar executable.
""",
),
"jacocorunner": attr.label(
cfg = "exec",
allow_single_file = True,
executable = True,
doc = """
Label of the JacocoCoverageRunner deploy jar.
""",
),
"javabuilder": attr.label(
cfg = "exec",
allow_single_file = True,
executable = True,
doc = """
Label of the JavaBuilder deploy jar.
""",
),
"javabuilder_data": attr.label_list(
cfg = "exec",
allow_files = True,
doc = """
Labels of data available for label-expansion in javabuilder_jvm_opts.
""",
),
"javabuilder_jvm_opts": attr.string_list(
doc = """
The list of arguments for the JVM when invoking JavaBuilder.
""",
),
"java_runtime": attr.label(
cfg = "exec",
providers = [JavaRuntimeInfo],
doc = """
The java_runtime to use with this toolchain. It defaults to java_runtime
in execution configuration.
""",
),
"javac_supports_workers": attr.bool(
default = True,
doc = """
True if JavaBuilder supports running as a persistent worker, false if it doesn't.
""",
),
"javac_supports_multiplex_workers": attr.bool(
default = True,
doc = """
True if JavaBuilder supports running as a multiplex persistent worker, false if it doesn't.
""",
),
"javac_supports_worker_cancellation": attr.bool(
default = True,
doc = """
True if JavaBuilder supports cancellation of persistent workers, false if it doesn't.
""",
),
"javac_supports_worker_multiplex_sandboxing": attr.bool(
default = False,
doc = """
True if JavaBuilder supports running as a multiplex persistent worker with sandboxing, false if it doesn't.
""",
),
"javacopts": attr.string_list(
default = [],
doc = """
The list of extra arguments for the Java compiler. Please refer to the Java compiler
documentation for the extensive list of possible Java compiler flags.
""",
),
"jspecify_implicit_deps": attr.label(
cfg = "exec",
allow_single_file = True,
executable = True,
doc = """Experimental, do not use!""",
),
"jspecify_javacopts": attr.string_list(
doc = """Experimental, do not use!""",
),
"jspecify_packages": attr.label_list(
cfg = "exec",
allow_files = True,
providers = [PackageSpecificationInfo],
doc = """Experimental, do not use!""",
),
"jspecify_processor": attr.label(
cfg = "exec",
allow_single_file = True,
executable = True,
doc = """Experimental, do not use!""",
),
"jspecify_processor_class": attr.string(
doc = """Experimental, do not use!""",
),
"jspecify_stubs": attr.label_list(
cfg = "exec",
allow_files = True,
doc = """Experimental, do not use!""",
),
"jvm_opts": attr.string_list(
default = [],
doc = """
The list of arguments for the JVM when invoking the Java compiler. Please refer to the Java
virtual machine documentation for the extensive list of possible flags for this option.
""",
),
"misc": attr.string_list(
default = [],
doc = """Deprecated: use javacopts instead""",
),
"oneversion": attr.label(
cfg = "exec",
allow_files = True,
executable = True,
doc = """
Label of the one-version enforcement binary.
""",
),
"oneversion_whitelist": attr.label(
allow_single_file = True,
doc = """Deprecated: use oneversion_allowlist instead""",
),
"oneversion_allowlist": attr.label(
allow_single_file = True,
doc = """
Label of the one-version allowlist.
""",
),
"oneversion_allowlist_for_tests": attr.label(
allow_single_file = True,
doc = """
Label of the one-version allowlist for tests.
""",
),
"package_configuration": attr.label_list(
cfg = "exec",
providers = [JavaPackageConfigurationInfo],
doc = """
Configuration that should be applied to the specified package groups.
""",
),
"proguard_allowlister": attr.label(
cfg = "exec",
executable = True,
allow_files = True,
default = semantics.PROGUARD_ALLOWLISTER_LABEL,
doc = """
Label of the Proguard allowlister.
""",
),
"reduced_classpath_incompatible_processors": attr.string_list(
doc = """Internal API, do not use!""",
),
"singlejar": attr.label(
cfg = "exec",
allow_files = True,
executable = True,
doc = """
Label of the SingleJar deploy jar.
""",
),
"source_version": attr.string(
doc = """
The Java source version (e.g., '6' or '7'). It specifies which set of code structures
are allowed in the Java source code.
""",
),
"target_version": attr.string(
doc = """
The Java target version (e.g., '6' or '7'). It specifies for which Java runtime the class
should be build.
""",
),
"timezone_data": attr.label(
cfg = "exec",
allow_single_file = True,
doc = """
Label of a resource jar containing timezone data. If set, the timezone data is added as an
implicitly runtime dependency of all java_binary rules.
""",
),
"tools": attr.label_list(
cfg = "exec",
allow_files = True,
doc = """
Labels of tools available for label-expansion in jvm_opts.
""",
),
"turbine_data": attr.label_list(
cfg = "exec",
allow_files = True,
doc = """
Labels of data available for label-expansion in turbine_jvm_opts.
""",
),
"turbine_jvm_opts": attr.string_list(
doc = """
The list of arguments for the JVM when invoking turbine.
""",
),
"xlint": attr.string_list(
default = [],
doc = """
The list of warning to add or removes from default list. Precedes it with a dash to
removes it. Please see the Javac documentation on the -Xlint options for more information.
""",
),
"licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
"_bytecode_optimizer": attr.label(
cfg = "exec",
executable = True,
default = configuration_field(fragment = "java", name = "java_toolchain_bytecode_optimizer"),
),
"_local_java_optimization_configuration": attr.label(
cfg = "exec",
default = configuration_field(fragment = "java", name = "local_java_optimization_configuration"),
allow_files = True,
),
"_legacy_any_type_attrs": attr.string_list(default = _LEGACY_ANY_TYPE_ATTRS),
},
fragments = ["java"],
)