blob: 9261cc3a649750bc243cf9014f3c64d6b4ddd041 [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.
""" Private utilities for Java compilation support in Starlark. """
load(":common/java/java_helper.bzl", "helper")
load(
":common/java/java_info.bzl",
"JavaPluginInfo",
"disable_plugin_info_annotation_processing",
"java_info_for_compilation",
"merge_plugin_info_without_outputs",
)
load(":common/java/java_semantics.bzl", "semantics")
load(":common/java/java_toolchain.bzl", "JavaToolchainInfo")
load(":common/java/sharded_javac.bzl", "experimental_sharded_javac", "use_sharded_javac")
load(":common/paths.bzl", "paths")
_java_common_internal = _builtins.internal.java_common_internal_do_not_use
def compile(
ctx,
output,
java_toolchain,
source_jars = [],
source_files = [],
output_source_jar = None,
javac_opts = [],
deps = [],
runtime_deps = [],
exports = [],
plugins = [],
exported_plugins = [],
native_libraries = [],
annotation_processor_additional_inputs = [],
annotation_processor_additional_outputs = [],
strict_deps = "ERROR",
bootclasspath = None,
sourcepath = [],
resources = [],
add_exports = [],
add_opens = [],
neverlink = False,
enable_annotation_processing = True,
# private to @_builtins:
enable_compile_jar_action = True,
enable_jspecify = True,
include_compilation_info = True,
classpath_resources = [],
resource_jars = [],
injecting_rule_kind = None):
"""Compiles Java source files/jars from the implementation of a Starlark rule
The result is a provider that represents the results of the compilation and can be added to the
set of providers emitted by this rule.
Args:
ctx: (RuleContext) The rule context
output: (File) The output of compilation
java_toolchain: (JavaToolchainInfo) Toolchain to be used for this compilation. Mandatory.
source_jars: ([File]) A list of the jars to be compiled. At least one of source_jars or
source_files should be specified.
source_files: ([File]) A list of the Java source files to be compiled. At least one of
source_jars or source_files should be specified.
output_source_jar: (File) The output source jar. Optional. Defaults to
`{output_jar}-src.jar` if unset.
javac_opts: ([str]) A list of the desired javac options. Optional.
deps: ([JavaInfo]) A list of dependencies. Optional.
runtime_deps: ([JavaInfo]) A list of runtime dependencies. Optional.
exports: ([JavaInfo]) A list of exports. Optional.
plugins: ([JavaPluginInfo|JavaInfo]) A list of plugins. Optional.
exported_plugins: ([JavaPluginInfo|JavaInfo]) A list of exported plugins. Optional.
native_libraries: ([CcInfo]) CC library dependencies that are needed for this library.
annotation_processor_additional_inputs: ([File]) A list of inputs that the Java compilation
action will take in addition to the Java sources for annotation processing.
annotation_processor_additional_outputs: ([File]) A list of outputs that the Java
compilation action will output in addition to the class jar from annotation processing.
strict_deps: (str) A string that specifies how to handle strict deps. Possible values:
'OFF', 'ERROR', 'WARN' and 'DEFAULT'.
bootclasspath: (BootClassPathInfo) If present, overrides the bootclasspath associated with
the provided java_toolchain. Optional.
sourcepath: ([File])
resources: ([File])
resource_jars: ([File])
classpath_resources: ([File])
neverlink: (bool)
enable_annotation_processing: (bool) Disables annotation processing in this compilation,
causing any annotation processors provided in plugins or in exported_plugins of deps to
be ignored.
enable_compile_jar_action: (bool) Enables header compilation or ijar creation. If set to
False, it forces use of the full class jar in the compilation classpaths of any
dependants. Doing so is intended for use by non-library targets such as binaries that
do not have dependants.
enable_jspecify: (bool)
include_compilation_info: (bool)
injecting_rule_kind: (str|None)
add_exports: ([str]) Allow this library to access the given <module>/<package>. Optional.
add_opens: ([str]) Allow this library to reflectively access the given <module>/<package>.
Optional.
Returns:
(JavaInfo)
"""
_java_common_internal.check_provider_instances([java_toolchain], "java_toolchain", JavaToolchainInfo)
_java_common_internal.check_provider_instances(plugins, "plugins", JavaPluginInfo)
plugin_info = merge_plugin_info_without_outputs(plugins + deps)
all_javac_opts = [] # [depset[str]]
all_javac_opts.append(java_toolchain._javacopts)
all_javac_opts.append(ctx.fragments.java.default_javac_flags_depset)
all_javac_opts.append(semantics.compatible_javac_options(
ctx,
java_toolchain,
_java_common_internal,
))
if ("com.google.devtools.build.runfiles.AutoBazelRepositoryProcessor" in
plugin_info.plugins.processor_classes.to_list()):
all_javac_opts.append(depset(
["-Abazel.repository=" + ctx.label.workspace_name],
order = "preorder",
))
for package_config in java_toolchain._package_configuration:
if package_config.matches(ctx.label):
all_javac_opts.append(package_config.javac_opts(as_depset = True))
all_javac_opts.append(depset(
["--add-exports=%s=ALL-UNNAMED" % x for x in add_exports],
order = "preorder",
))
# detokenize target's javacopts, it will be tokenized before compilation
all_javac_opts.append(helper.detokenize_javacopts(helper.tokenize_javacopts(ctx, javac_opts)))
# we reverse the list of javacopts depsets, so that we keep the right-most set
# in case it's deduped. When this depset is flattened, we will reverse again,
# and then tokenize before passing to javac. This way, right-most javacopts will
# be retained and "win out".
all_javac_opts = depset(order = "preorder", transitive = reversed(all_javac_opts))
# Optimization: skip this if there are no annotation processors, to avoid unnecessarily
# disabling the direct classpath optimization if `enable_annotation_processor = False`
# but there aren't any annotation processors.
enable_direct_classpath = True
if not enable_annotation_processing and plugin_info.plugins.processor_classes:
plugin_info = disable_plugin_info_annotation_processing(plugin_info)
enable_direct_classpath = False
all_javac_opts_list = helper.tokenize_javacopts(ctx, all_javac_opts)
uses_annotation_processing = False
if "-processor" in all_javac_opts_list or plugin_info.plugins.processor_classes:
uses_annotation_processing = True
has_sources = source_files or source_jars
has_resources = resources or resource_jars
is_strict_mode = strict_deps != "OFF"
classpath_mode = ctx.fragments.java.reduce_java_classpath()
direct_jars = depset()
if is_strict_mode:
direct_jars = depset(order = "preorder", transitive = [dep.compile_jars for dep in deps])
compilation_classpath = depset(
order = "preorder",
transitive = [direct_jars] + [dep.transitive_compile_time_jars for dep in deps],
)
compile_time_java_deps = depset()
if is_strict_mode and classpath_mode != "OFF":
compile_time_java_deps = depset(transitive = [dep._compile_time_java_dependencies for dep in deps])
# create compile time jar action
if not has_sources:
compile_jar = None
compile_deps_proto = None
elif not enable_compile_jar_action:
compile_jar = output
compile_deps_proto = None
elif _should_use_header_compilation(ctx, java_toolchain):
compile_jar = helper.derive_output_file(ctx, output, name_suffix = "-hjar", extension = "jar")
compile_deps_proto = helper.derive_output_file(ctx, output, name_suffix = "-hjar", extension = "jdeps")
_java_common_internal.create_header_compilation_action(
ctx,
java_toolchain,
compile_jar,
compile_deps_proto,
plugin_info,
depset(source_files),
source_jars,
compilation_classpath,
direct_jars,
bootclasspath,
compile_time_java_deps,
all_javac_opts,
strict_deps,
ctx.label,
injecting_rule_kind,
enable_direct_classpath,
annotation_processor_additional_inputs,
)
elif ctx.fragments.java.use_ijars():
compile_jar = run_ijar(
ctx.actions,
output,
java_toolchain,
target_label = ctx.label,
injecting_rule_kind = injecting_rule_kind,
)
compile_deps_proto = None
else:
compile_jar = output
compile_deps_proto = None
if use_sharded_javac(ctx):
if compile_jar == output or not compile_jar:
fail("sharding requested without hjar/ijar compilation")
generated_source_jar = None
generated_class_jar = None
deps_proto = None
native_headers_jar = None
manifest_proto = None
experimental_sharded_javac(
ctx,
java_toolchain,
output,
compile_jar,
plugin_info,
compilation_classpath,
direct_jars,
bootclasspath,
compile_time_java_deps,
all_javac_opts,
strict_deps,
source_files,
source_jars,
resources,
resource_jars,
)
else:
native_headers_jar = helper.derive_output_file(ctx, output, name_suffix = "-native-header")
manifest_proto = helper.derive_output_file(ctx, output, extension_suffix = "_manifest_proto")
deps_proto = None
if ctx.fragments.java.generate_java_deps() and has_sources:
deps_proto = helper.derive_output_file(ctx, output, extension = "jdeps")
generated_class_jar = None
generated_source_jar = None
if uses_annotation_processing:
generated_class_jar = helper.derive_output_file(ctx, output, name_suffix = "-gen")
generated_source_jar = helper.derive_output_file(ctx, output, name_suffix = "-gensrc")
_java_common_internal.create_compilation_action(
ctx,
java_toolchain,
output,
manifest_proto,
plugin_info,
compilation_classpath,
direct_jars,
bootclasspath,
compile_time_java_deps,
all_javac_opts,
strict_deps,
ctx.label,
deps_proto,
generated_class_jar,
generated_source_jar,
native_headers_jar,
depset(source_files),
source_jars,
resources,
depset(resource_jars),
classpath_resources,
sourcepath,
injecting_rule_kind,
enable_jspecify,
enable_direct_classpath,
annotation_processor_additional_inputs,
annotation_processor_additional_outputs,
)
create_output_source_jar = len(source_files) > 0 or source_jars != [output_source_jar]
if not output_source_jar:
output_source_jar = helper.derive_output_file(ctx, output, name_suffix = "-src", extension = "jar")
if create_output_source_jar:
helper.create_single_jar(
ctx.actions,
toolchain = java_toolchain,
output = output_source_jar,
sources = depset(source_jars + ([generated_source_jar] if generated_source_jar else [])),
resources = depset(source_files),
progress_message = "Building source jar %{output}",
mnemonic = "JavaSourceJar",
)
if has_sources or has_resources:
direct_runtime_jars = [output]
else:
direct_runtime_jars = []
compilation_info = struct(
javac_options = all_javac_opts_list,
javac_options_list = all_javac_opts_list,
# needs to be flattened because the public API is a list
boot_classpath = (bootclasspath.bootclasspath if bootclasspath else java_toolchain.bootclasspath).to_list(),
# we only add compile time jars from deps, and not exports
compilation_classpath = compilation_classpath,
runtime_classpath = depset(
order = "preorder",
direct = direct_runtime_jars,
transitive = [dep.transitive_runtime_jars for dep in runtime_deps + deps],
),
uses_annotation_processing = uses_annotation_processing,
) if include_compilation_info else None
return java_info_for_compilation(
output_jar = output,
compile_jar = compile_jar,
source_jar = output_source_jar,
generated_class_jar = generated_class_jar,
generated_source_jar = generated_source_jar,
plugin_info = plugin_info,
deps = deps,
runtime_deps = runtime_deps,
exports = exports,
exported_plugins = exported_plugins,
compile_jdeps = compile_deps_proto if compile_deps_proto else deps_proto,
jdeps = deps_proto if include_compilation_info else None,
native_headers_jar = native_headers_jar,
manifest_proto = manifest_proto,
native_libraries = native_libraries,
neverlink = neverlink,
add_exports = add_exports,
add_opens = add_opens,
direct_runtime_jars = direct_runtime_jars,
compilation_info = compilation_info,
)
def _should_use_header_compilation(ctx, toolchain):
if not ctx.fragments.java.use_header_compilation():
return False
if toolchain._forcibly_disable_header_compilation:
return False
if not toolchain._header_compiler:
fail(
"header compilation was requested but it is not supported by the " +
"current Java toolchain '" + str(toolchain.label) +
"'; see the java_toolchain.header_compiler attribute",
)
if not toolchain._header_compiler_direct:
fail(
"header compilation was requested but it is not supported by the " +
"current Java toolchain '" + str(toolchain.label) +
"'; see the java_toolchain.header_compiler_direct attribute",
)
return True
def run_ijar(
actions,
jar,
java_toolchain,
target_label = None,
# private to @_builtins:
output = None,
injecting_rule_kind = None):
"""Runs ijar on a jar, stripping it of its method bodies.
This helps reduce rebuilding of dependent jars during any recompiles consisting only of simple
changes to method implementations. The return value is typically passed to JavaInfo.compile_jar
Args:
actions: (actions) ctx.actions
jar: (File) The jar to run ijar on.
java_toolchain: (JavaToolchainInfo) The toolchain to used to find the ijar tool.
target_label: (Label|None) A target label to stamp the jar with. Used for `add_dep` support.
Typically, you would pass `ctx.label` to stamp the jar with the current rule's label.
output: (File) Optional.
injecting_rule_kind: (str) the rule class of the current target
Returns:
(File) The output artifact
"""
if not output:
output = actions.declare_file(paths.replace_extension(jar.basename, "-ijar.jar"), sibling = jar)
args = actions.args()
args.add(jar)
args.add(output)
if target_label != None:
args.add("--target_label", target_label)
if injecting_rule_kind != None:
args.add("--injecting_rule_kind", injecting_rule_kind)
actions.run(
mnemonic = "JavaIjar",
inputs = [jar],
outputs = [output],
executable = java_toolchain.ijar,
arguments = [args],
progress_message = "Extracting interface for jar %{input}",
toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
use_default_shell_env = True,
)
return output
def target_kind(target):
"""Get the rule class string for a target
Args:
target: (Target)
Returns:
(str) The rule class string of the target
"""
return _java_common_internal.target_kind(target)
def get_build_info(ctx, is_stamping_enabled):
"""Get the artifacts representing the workspace status for this build
Args:
ctx: (RuleContext) The rule context
is_stamping_enabled: (bool) If stamping is enabled
Returns
([File]) The build info artifacts
"""
return _java_common_internal.get_build_info(ctx, is_stamping_enabled)
def collect_native_deps_dirs(libraries):
"""Collect the set of root-relative paths containing native libraries
Args:
libraries: (depset[LibraryToLink]) set of native libraries
Returns:
([String]) A set of root-relative paths as a list
"""
return _java_common_internal.collect_native_deps_dirs(libraries)
def get_runtime_classpath_for_archive(jars, excluded_jars):
"""Filters a classpath to remove certain entries
Args
jars: (depset[File]) The classpath to filter
excluded_jars: (depset[File]) The files to remove
Returns:
(depset[File]) The filtered classpath
"""
return _java_common_internal.get_runtime_classpath_for_archive(
jars,
excluded_jars,
)
def filter_protos_for_generated_extension_registry(runtime_jars, deploy_env):
"""Get proto artifacts from runtime_jars excluding those in deploy_env
Args:
runtime_jars: (depset[File]) the artifacts to scan
deploy_env: (depset[File]) the artifacts to exclude
Returns
(depset[File], bool) A tuple of the filtered protos and whether all protos are 'lite'
flavored
"""
return _java_common_internal.filter_protos_for_generated_extension_registry(
runtime_jars,
deploy_env,
)