blob: ee274adee7852f83aeb00f0b28ba607b8c57016c [file] [log] [blame]
# Copyright 2025 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.
"""Common util functions for java_* rules"""
load("@bazel_skylib//lib:paths.bzl", "paths")
load("//java/common:java_semantics.bzl", "semantics")
# copybara: rules_java visibility
def _java_segments(path):
if path.startswith("/"):
fail("path must not be absolute: '%s'" % path)
segments = path.split("/")
root_idx = -1
for idx, segment in enumerate(segments):
if segment in ["java", "javatests", "src", "testsrc"]:
root_idx = idx
break
if root_idx < 0:
return None
is_src = "src" == segments[root_idx]
check_mvn_idx = root_idx if is_src else -1
if (root_idx == 0 or is_src):
for i in range(root_idx + 1, len(segments) - 1):
segment = segments[i]
if "src" == segment or (is_src and (segment in ["java", "javatests"])):
next = segments[i + 1]
if next in ["com", "org", "net"]:
root_idx = i
elif "src" == segment:
check_mvn_idx = i
break
if check_mvn_idx >= 0 and check_mvn_idx < len(segments) - 2:
next = segments[check_mvn_idx + 1]
if next in ["main", "test"]:
next = segments[check_mvn_idx + 2]
if next in ["java", "resources"]:
root_idx = check_mvn_idx + 2
return segments[(root_idx + 1):]
def _has_target_constraints(ctx, constraints):
# Constraints is a label_list.
for constraint in constraints:
constraint_value = constraint[platform_common.ConstraintValueInfo]
if ctx.target_platform_has_constraint(constraint_value):
return True
return False
def _is_target_platform_windows(ctx):
return _has_target_constraints(ctx, ctx.attr._windows_constraints)
def _resource_mapper(file):
root_relative_path = paths.relativize(
path = file.path,
start = paths.join(file.root.path, file.owner.workspace_root),
)
return "%s:%s" % (
file.path,
semantics.get_default_resource_path(root_relative_path, segment_extractor = _java_segments),
)
def _create_single_jar(
actions,
toolchain,
output,
sources = depset(),
resources = depset(),
mnemonic = "JavaSingleJar",
progress_message = "Building singlejar jar %{output}",
build_target = None,
output_creator = None):
"""Register singlejar action for the output jar.
Args:
actions: (actions) ctx.actions
toolchain: (JavaToolchainInfo) The java toolchain
output: (File) Output file of the action.
sources: (depset[File]) The jar files to merge into the output jar.
resources: (depset[File]) The files to add to the output jar.
mnemonic: (str) The action identifier
progress_message: (str) The action progress message
build_target: (Label) The target label to stamp in the manifest. Optional.
output_creator: (str) The name of the tool to stamp in the manifest. Optional,
defaults to 'singlejar'
Returns:
(File) Output file which was used for registering the action.
"""
args = actions.args()
args.set_param_file_format("shell").use_param_file("@%s", use_always = True)
args.add("--output", output)
args.add_all(
[
"--compression",
"--normalize",
"--exclude_build_data",
"--warn_duplicate_resources",
],
)
args.add_all("--sources", sources)
args.add_all("--resources", resources, map_each = _resource_mapper)
args.add("--build_target", build_target)
args.add("--output_jar_creator", output_creator)
actions.run(
mnemonic = mnemonic,
progress_message = progress_message,
executable = toolchain.single_jar,
toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
inputs = depset(transitive = [resources, sources]),
tools = [toolchain.single_jar],
outputs = [output],
arguments = [args],
use_default_shell_env = True,
)
return output
# TODO(hvd): use skylib shell.quote()
def _shell_escape(s):
"""Shell-escape a string
Quotes a word so that it can be used, without further quoting, as an argument
(or part of an argument) in a shell command.
Args:
s: (str) the string to escape
Returns:
(str) the shell-escaped string
"""
if not s:
# Empty string is a special case: needs to be quoted to ensure that it
# gets treated as a separate argument.
return "''"
for c in s.elems():
# We do this positively so as to be sure we don't inadvertently forget
# any unsafe characters.
if not c.isalnum() and c not in "@%-_+:,./":
return "'" + s.replace("'", "'\\''") + "'"
return s
def _detokenize_javacopts(opts):
"""Detokenizes a list of options to a depset.
Args:
opts: ([str]) the javac options to detokenize
Returns:
(depset[str]) depset of detokenized options
"""
return depset(
[" ".join([_shell_escape(opt) for opt in opts])],
order = "preorder",
)
def _get_relative(path_a, path_b):
if paths.is_absolute(path_b):
return path_b
return paths.normalize(paths.join(path_a, path_b))
def _tokenize_javacopts(ctx = None, opts = []):
"""Tokenizes a list or depset of options to a list.
Iff opts is a depset, we reverse the flattened list to ensure right-most
duplicates are preserved in their correct position.
If the ctx parameter is omitted, a slow, but pure Starlark, implementation
of shell tokenization is used. Otherwise, tokenization is performed using
ctx.tokenize() which has significantly better performance (up to 100x for
large options lists).
Args:
ctx: (RuleContext|None) the rule context
opts: (depset[str]|[str]) the javac options to tokenize
Returns:
[str] list of tokenized options
"""
if hasattr(opts, "to_list"):
opts = reversed(opts.to_list())
if ctx:
return [
token
for opt in opts
for token in ctx.tokenize(opt)
]
else:
# TODO: optimize and use the pure Starlark implementation in cc_helper
return semantics.tokenize_javacopts(opts)
helper = struct(
is_target_platform_windows = _is_target_platform_windows,
create_single_jar = _create_single_jar,
shell_escape = _shell_escape,
detokenize_javacopts = _detokenize_javacopts,
tokenize_javacopts = _tokenize_javacopts,
get_relative = _get_relative,
has_target_constraints = _has_target_constraints,
java_segments = _java_segments,
)