fwd/vendor/cxx/tools/buck/prelude/java/java_library.bzl
John Doty 9c435dc440 Vendor dependencies
Let's see how I like this workflow.
2022-12-19 08:38:22 -08:00

639 lines
24 KiB
Python

# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under both the MIT license found in the
# LICENSE-MIT file in the root directory of this source tree and the Apache
# License, Version 2.0 found in the LICENSE-APACHE file in the root directory
# of this source tree.
load("@prelude//:paths.bzl", "paths")
load("@prelude//android:android_providers.bzl", "merge_android_packageable_info")
load(
"@prelude//java:java_providers.bzl",
"JavaLibraryInfo",
"JavaPackagingDepTSet",
"JavaProviders",
"create_abi",
"create_java_library_providers",
"create_native_providers",
"derive_compiling_deps",
"make_compile_outputs",
"to_list",
)
load("@prelude//java:java_resources.bzl", "get_resources_map")
load("@prelude//java:java_toolchain.bzl", "JavaToolchainInfo")
load("@prelude//java:javacd_jar_creator.bzl", "create_jar_artifact_javacd")
load("@prelude//java/plugins:java_annotation_processor.bzl", "create_ap_params")
load("@prelude//java/plugins:java_plugin.bzl", "PluginParams", "create_plugin_params")
load("@prelude//java/utils:java_utils.bzl", "derive_javac", "get_abi_generation_mode", "get_default_info", "get_java_version_attributes", "get_path_separator", "to_java_version")
load("@prelude//linking:shared_libraries.bzl", "SharedLibraryInfo")
load("@prelude//utils:utils.bzl", "expect")
_JAVA_FILE_EXTENSION = [".java"]
_SUPPORTED_ARCHIVE_SUFFIXES = [".src.zip", "-sources.jar"]
def _process_classpath(
actions: "actions",
classpath_args: "cmd_args",
cmd: "cmd_args",
args_file_name: "string",
option_name: "string"):
# write joined classpath string into args file
classpath_args_file, _ = actions.write(
args_file_name,
classpath_args,
allow_args = True,
)
# mark classpath artifacts as input
cmd.hidden(classpath_args)
# add classpath args file to cmd
cmd.add(option_name, classpath_args_file)
def classpath_args(args):
return cmd_args(args, delimiter = get_path_separator())
def _process_plugins(
actions: "actions",
actions_prefix: str.type,
ap_params: ["AnnotationProcessorParams"],
plugin_params: ["PluginParams", None],
javac_args: "cmd_args",
cmd: "cmd_args"):
processors_classpath_tsets = []
# Process Annotation processors
if ap_params:
# For external javac, we can't preserve separate classpaths for separate processors. So we just concat everything.
javac_args.add("-processor")
joined_processors_string = ",".join([p for ap in ap_params for p in ap.processors])
javac_args.add(joined_processors_string)
for ap in ap_params:
for param in ap.params:
javac_args.add("-A{}".format(param))
if ap.deps:
processors_classpath_tsets.append(ap.deps)
else:
javac_args.add("-proc:none")
# Process Javac Plugins
if plugin_params:
plugin = plugin_params.processors[0]
args = plugin_params.args.get(plugin, cmd_args())
# Produces "-Xplugin:PluginName arg1 arg2 arg3", as a single argument
plugin_and_args = cmd_args(plugin)
plugin_and_args.add(args)
plugin_arg = cmd_args(format = "-Xplugin:{}", quote = "shell")
plugin_arg.add(cmd_args(plugin_and_args, delimiter = " "))
javac_args.add(plugin_arg)
if plugin_params.deps:
processors_classpath_tsets.append(plugin_params.deps)
if len(processors_classpath_tsets) > 1:
processors_classpath_tset = actions.tset(JavaPackagingDepTSet, children = processors_classpath_tsets)
elif len(processors_classpath_tsets) == 1:
processors_classpath_tset = processors_classpath_tsets[0]
else:
processors_classpath_tset = None
if processors_classpath_tset:
processors_classpath = classpath_args(processors_classpath_tset.project_as_args("full_jar_args"))
_process_classpath(
actions,
processors_classpath,
cmd,
"{}plugin_cp_args".format(actions_prefix),
"--javac_processors_classpath_file",
)
def _build_classpath(actions: "actions", deps: ["dependency"], additional_classpath_entries: ["artifact"], classpath_args_projection: "string") -> ["cmd_args", None]:
compiling_deps_tset = derive_compiling_deps(actions, None, deps)
if additional_classpath_entries or compiling_deps_tset:
args = cmd_args()
if compiling_deps_tset:
args.add(compiling_deps_tset.project_as_args(classpath_args_projection))
args.add(additional_classpath_entries)
return args
return None
def _build_bootclasspath(bootclasspath_entries: ["artifact"], source_level: int.type, java_toolchain: "JavaToolchainInfo") -> ["artifact"]:
bootclasspath_list = []
if source_level in [7, 8]:
if bootclasspath_entries:
bootclasspath_list = bootclasspath_entries
elif source_level == 7:
bootclasspath_list = java_toolchain.bootclasspath_7
elif source_level == 8:
bootclasspath_list = java_toolchain.bootclasspath_8
return bootclasspath_list
def _append_javac_params(
actions: "actions",
actions_prefix: str.type,
java_toolchain: "JavaToolchainInfo",
srcs: ["artifact"],
remove_classes: [str.type],
annotation_processor_params: ["AnnotationProcessorParams"],
javac_plugin_params: ["PluginParams", None],
source_level: int.type,
target_level: int.type,
deps: ["dependency"],
extra_arguments: ["string"],
additional_classpath_entries: ["artifact"],
bootclasspath_entries: ["artifact"],
cmd: "cmd_args",
generated_sources_dir: "artifact"):
javac_args = cmd_args(
"-encoding",
"utf-8",
# Set the sourcepath to stop us reading source files out of jars by mistake.
"-sourcepath",
'""',
)
javac_args.add(*extra_arguments)
# we want something that looks nice when prepended to another string, so add "_" to non-empty prefixes.
if actions_prefix:
actions_prefix += "_"
compiling_classpath = _build_classpath(actions, deps, additional_classpath_entries, "args_for_compiling")
if compiling_classpath:
_process_classpath(
actions,
classpath_args(compiling_classpath),
cmd,
"{}classpath_args".format(actions_prefix),
"--javac_classpath_file",
)
else:
javac_args.add("-classpath ''")
javac_args.add("-source")
javac_args.add(str(source_level))
javac_args.add("-target")
javac_args.add(str(target_level))
bootclasspath_list = _build_bootclasspath(bootclasspath_entries, source_level, java_toolchain)
if bootclasspath_list:
_process_classpath(
actions,
classpath_args(bootclasspath_list),
cmd,
"{}bootclasspath_args".format(actions_prefix),
"--javac_bootclasspath_file",
)
_process_plugins(
actions,
actions_prefix,
annotation_processor_params,
javac_plugin_params,
javac_args,
cmd,
)
cmd.add("--generated_sources_dir", generated_sources_dir.as_output())
zipped_sources, plain_sources = split_on_archives_and_plain_files(srcs, _JAVA_FILE_EXTENSION)
javac_args.add(*plain_sources)
args_file, _ = actions.write(
"{}javac_args".format(actions_prefix),
javac_args,
allow_args = True,
)
cmd.hidden(javac_args)
# mark plain srcs artifacts as input
cmd.hidden(plain_sources)
cmd.add("--javac_args_file", args_file)
if zipped_sources:
cmd.add("--zipped_sources_file", actions.write("{}zipped_source_args".format(actions_prefix), zipped_sources))
cmd.hidden(zipped_sources)
if remove_classes:
cmd.add("--remove_classes", actions.write("{}remove_classes_args".format(actions_prefix), remove_classes))
def split_on_archives_and_plain_files(
srcs: ["artifact"],
plain_file_extensions: [str.type]) -> (["artifact"], ["artifact"]):
archives = []
plain_sources = []
for src in srcs:
if src.extension in plain_file_extensions:
plain_sources.append(src)
elif _is_supported_archive(src):
archives.append(src)
else:
fail("Provided java source is not supported: {}".format(src))
return (archives, plain_sources)
def _is_supported_archive(src: "artifact") -> bool.type:
basename = src.basename
for supported_suffix in _SUPPORTED_ARCHIVE_SUFFIXES:
if basename.endswith(supported_suffix):
return True
return False
def _copy_resources(
actions: "actions",
actions_prefix: str.type,
java_toolchain: JavaToolchainInfo.type,
package: str.type,
resources: ["artifact"],
resources_root: [str.type, None]) -> "artifact":
resources_to_copy = get_resources_map(java_toolchain, package, resources, resources_root)
resource_output = actions.symlinked_dir("{}resources".format(actions_prefix), resources_to_copy)
return resource_output
def _jar_creator(
javac_tool: ["", None],
java_toolchain: JavaToolchainInfo.type) -> "function":
if javac_tool or java_toolchain.javac_protocol == "classic":
return _create_jar_artifact
elif java_toolchain.javac_protocol == "javacd":
return create_jar_artifact_javacd
else:
fail("unrecognized javac protocol `{}`".format(java_toolchain.javac_protocol))
def compile_to_jar(
ctx: "context",
srcs: ["artifact"],
*,
abi_generation_mode: ["AbiGenerationMode", None] = None,
output: ["artifact", None] = None,
actions_prefix: [str.type, None] = None,
javac_tool: ["", None] = None,
resources: [["artifact"], None] = None,
resources_root: [str.type, None] = None,
remove_classes: [[str.type], None] = None,
manifest_file: ["artifact", None] = None,
ap_params: [["AnnotationProcessorParams"], None] = None,
plugin_params: ["PluginParams", None] = None,
source_level: [int.type, None] = None,
target_level: [int.type, None] = None,
deps: [["dependency"], None] = None,
required_for_source_only_abi: bool.type = False,
source_only_abi_deps: [["dependency"], None] = None,
extra_arguments: [["string"], None] = None,
additional_classpath_entries: [["artifact"], None] = None,
additional_compiled_srcs: ["artifact", None] = None,
bootclasspath_entries: [["artifact"], None] = None) -> "JavaCompileOutputs":
if not additional_classpath_entries:
additional_classpath_entries = []
if not bootclasspath_entries:
bootclasspath_entries = []
if not extra_arguments:
extra_arguments = []
if not resources:
resources = []
if not deps:
deps = []
if not remove_classes:
remove_classes = []
if not actions_prefix:
actions_prefix = ""
if not ap_params:
ap_params = []
if not source_only_abi_deps:
source_only_abi_deps = []
# TODO(cjhopman): Should verify that source_only_abi_deps are contained within the normal classpath.
java_toolchain = ctx.attrs._java_toolchain[JavaToolchainInfo]
if not source_level:
source_level = to_java_version(java_toolchain.source_level)
if not target_level:
target_level = to_java_version(java_toolchain.target_level)
is_building_android_binary = ctx.attrs._is_building_android_binary
return _jar_creator(javac_tool, java_toolchain)(
ctx.actions,
actions_prefix,
abi_generation_mode,
java_toolchain,
ctx.label,
output,
javac_tool,
srcs,
remove_classes,
resources,
resources_root,
manifest_file,
ap_params,
plugin_params,
source_level,
target_level,
deps,
required_for_source_only_abi,
source_only_abi_deps,
extra_arguments,
additional_classpath_entries,
additional_compiled_srcs,
bootclasspath_entries,
is_building_android_binary,
)
def _create_jar_artifact(
actions: "actions",
actions_prefix: str.type,
_abi_generation_mode: ["AbiGenerationMode", None],
java_toolchain: JavaToolchainInfo.type,
label: "label",
output: ["artifact", None],
javac_tool: ["", None],
srcs: ["artifact"],
remove_classes: [str.type],
resources: ["artifact"],
resources_root: [str.type, None],
manifest_file: ["artifact", None],
ap_params: ["AnnotationProcessorParams"],
plugin_params: ["PluginParams", None],
source_level: int.type,
target_level: int.type,
deps: ["dependency"],
required_for_source_only_abi: bool.type,
_source_only_abi_deps: ["dependency"],
extra_arguments: ["string"],
additional_classpath_entries: ["artifact"],
additional_compiled_srcs: ["artifact", None],
bootclasspath_entries: ["artifact"],
_is_building_android_binary: bool.type) -> "JavaCompileOutputs":
"""
Creates jar artifact.
Returns a single artifacts that represents jar output file
"""
javac_tool = javac_tool or java_toolchain.javac
jar_out = output or actions.declare_output(paths.join(actions_prefix or "jar", "lib.jar"))
args = [
java_toolchain.compile_and_package[RunInfo],
"--jar_builder_tool",
cmd_args(java_toolchain.jar_builder, delimiter = " "),
"--output",
jar_out.as_output(),
]
skip_javac = False if srcs or ap_params or plugin_params else True
if skip_javac:
args.append("--skip_javac_run")
else:
args += ["--javac_tool", javac_tool]
if resources:
resource_dir = _copy_resources(actions, actions_prefix, java_toolchain, label.package, resources, resources_root)
args += ["--resources_dir", resource_dir]
if manifest_file:
args += ["--manifest", manifest_file]
if additional_compiled_srcs:
args += ["--additional_compiled_srcs", additional_compiled_srcs]
compile_and_package_cmd = cmd_args(args)
generated_sources_dir = None
if not skip_javac:
generated_sources_dir = actions.declare_output("{}generated_sources".format(actions_prefix))
_append_javac_params(
actions,
actions_prefix,
java_toolchain,
srcs,
remove_classes,
ap_params,
plugin_params,
source_level,
target_level,
deps,
extra_arguments,
additional_classpath_entries,
bootclasspath_entries,
compile_and_package_cmd,
generated_sources_dir,
)
actions.run(compile_and_package_cmd, category = "javac_and_jar", identifier = actions_prefix)
abi = None if java_toolchain.is_bootstrap_toolchain else create_abi(actions, java_toolchain.class_abi_generator, jar_out)
return make_compile_outputs(
full_library = jar_out,
class_abi = abi,
required_for_source_only_abi = required_for_source_only_abi,
annotation_processor_output = generated_sources_dir,
)
def _check_dep_types(deps: ["dependency"]):
for dep in deps:
if JavaLibraryInfo not in dep and SharedLibraryInfo not in dep:
fail("Received dependency {} is not supported. `java_library`, `prebuilt_jar` and native libraries are supported.".format(dep))
def _check_provided_deps(provided_deps: ["dependency"], attr_name: str.type):
for provided_dep in provided_deps:
expect(
JavaLibraryInfo in provided_dep or SharedLibraryInfo not in provided_dep,
"Java code does not need native libs in order to compile, so not valid as {}: {}".format(attr_name, provided_dep),
)
def _check_exported_deps(exported_deps: ["dependency"], attr_name: str.type):
for exported_dep in exported_deps:
expect(
JavaLibraryInfo in exported_dep,
"Exported deps are meant to be forwarded onto the classpath for dependents, so only " +
"make sense for a target that emits Java bytecode, {} in {} does not.".format(exported_dep, attr_name),
)
# TODO(T108258238) remove need for this
def _skip_java_library_dep_checks(ctx: "context") -> bool.type:
return "skip_buck2_java_library_dep_checks" in ctx.attrs.labels
def java_library_impl(ctx: "context") -> ["provider"]:
"""
java_library() rule implementation
Args:
ctx: rule analysis context
Returns:
list of created providers
"""
packaging_deps = ctx.attrs.deps + ctx.attrs.exported_deps + ctx.attrs.runtime_deps
if ctx.attrs._build_only_native_code:
shared_library_info, cxx_resource_info = create_native_providers(ctx.actions, ctx.label, packaging_deps)
return [
shared_library_info,
cxx_resource_info,
# Add an unused default output in case this target is used an an attr.source() anywhere.
DefaultInfo(default_outputs = [ctx.actions.write("unused.jar", [])]),
TemplatePlaceholderInfo(keyed_variables = {
"classpath": "unused_but_needed_for_analysis",
}),
]
if not _skip_java_library_dep_checks(ctx):
_check_dep_types(ctx.attrs.deps)
_check_dep_types(ctx.attrs.provided_deps)
_check_dep_types(ctx.attrs.exported_deps)
_check_dep_types(ctx.attrs.exported_provided_deps)
_check_dep_types(ctx.attrs.runtime_deps)
java_providers = build_java_library(ctx, ctx.attrs.srcs)
return to_list(java_providers) + [
# TODO(T107163344) this shouldn't be in java_library itself, use overlays to remove it.
merge_android_packageable_info(
ctx.label,
ctx.actions,
ctx.attrs.deps + ctx.attrs.exported_deps + ctx.attrs.runtime_deps,
),
]
def build_java_library(
ctx: "context",
srcs: ["artifact"],
run_annotation_processors = True,
additional_classpath_entries: ["artifact"] = [],
bootclasspath_entries: ["artifact"] = [],
additional_compiled_srcs: ["artifact", None] = None,
generated_sources: ["artifact"] = [],
override_abi_generation_mode: ["AbiGenerationMode", None] = None) -> JavaProviders.type:
expect(
not getattr(ctx.attrs, "_build_only_native_code", False),
"Shouldn't call build_java_library if we're only building native code!",
)
_check_provided_deps(ctx.attrs.provided_deps, "provided_deps")
_check_provided_deps(ctx.attrs.exported_provided_deps, "exported_provided_deps")
_check_exported_deps(ctx.attrs.exported_deps, "exported_deps")
_check_exported_deps(ctx.attrs.exported_provided_deps, "exported_provided_deps")
deps_query = getattr(ctx.attrs, "deps_query", []) or []
provided_deps_query = getattr(ctx.attrs, "provided_deps_query", []) or []
first_order_deps = (
ctx.attrs.deps +
deps_query +
ctx.attrs.exported_deps +
ctx.attrs.provided_deps +
provided_deps_query +
ctx.attrs.exported_provided_deps
)
resources = ctx.attrs.resources
ap_params = create_ap_params(
ctx,
ctx.attrs.plugins,
ctx.attrs.annotation_processors,
ctx.attrs.annotation_processor_params,
ctx.attrs.annotation_processor_deps,
) if run_annotation_processors else None
plugin_params = create_plugin_params(ctx, ctx.attrs.plugins) if run_annotation_processors else None
manifest_file = ctx.attrs.manifest_file
source_level, target_level = get_java_version_attributes(ctx)
javac_tool = derive_javac(ctx.attrs.javac) if ctx.attrs.javac else None
outputs = None
common_compile_kwargs = None
sub_targets = {}
if srcs or additional_compiled_srcs or resources or ap_params or plugin_params or manifest_file:
abi_generation_mode = override_abi_generation_mode or get_abi_generation_mode(ctx.attrs.abi_generation_mode)
common_compile_kwargs = {
"abi_generation_mode": abi_generation_mode,
"additional_classpath_entries": additional_classpath_entries,
"additional_compiled_srcs": additional_compiled_srcs,
"ap_params": ap_params,
"bootclasspath_entries": bootclasspath_entries,
"deps": first_order_deps,
"extra_arguments": ctx.attrs.extra_arguments,
"manifest_file": manifest_file,
"remove_classes": ctx.attrs.remove_classes,
"required_for_source_only_abi": ctx.attrs.required_for_source_only_abi,
"resources": resources,
"resources_root": ctx.attrs.resources_root,
"source_level": source_level,
"source_only_abi_deps": ctx.attrs.source_only_abi_deps,
"srcs": srcs,
"target_level": target_level,
}
outputs = compile_to_jar(
ctx,
javac_tool = javac_tool,
plugin_params = plugin_params,
**common_compile_kwargs
)
java_toolchain = ctx.attrs._java_toolchain[JavaToolchainInfo]
if (
common_compile_kwargs and
srcs and
not java_toolchain.is_bootstrap_toolchain and
not ctx.attrs._is_building_android_binary
):
ast_dumper = java_toolchain.ast_dumper
# Replace whatever compiler plugins are present with the AST dumper instead
ast_output = ctx.actions.declare_output("ast_json")
dump_ast_args = cmd_args(ast_output.as_output(), "--target-label", '"{}"'.format(ctx.label))
for dep in _build_bootclasspath(bootclasspath_entries, source_level, java_toolchain):
dump_ast_args.add("--dependency", '"{}"'.format(dep.owner), dep)
classpath_args = _build_classpath(ctx.actions, first_order_deps, additional_classpath_entries, "args_for_ast_dumper")
if classpath_args:
dump_ast_args.add(classpath_args)
ast_dumping_plugin_params = create_plugin_params(ctx, [ast_dumper])
ast_dumper_args_file = ctx.actions.write("dump_ast_args", dump_ast_args)
ast_dumping_plugin_params = PluginParams(
processors = ast_dumping_plugin_params.processors,
deps = ast_dumping_plugin_params.deps,
args = {
"DumpAstPlugin": cmd_args(ast_dumper_args_file).hidden(dump_ast_args),
},
)
# We don't actually care about the jar output this time; we just want the AST from the
# plugin
compile_to_jar(
ctx,
actions_prefix = "ast",
plugin_params = ast_dumping_plugin_params,
javac_tool = java_toolchain.fallback_javac,
**common_compile_kwargs
)
sub_targets["ast"] = [DefaultInfo(default_outputs = [ast_output])]
java_library_info, java_packaging_info, shared_library_info, cxx_resource_info, template_placeholder_info, intellij_info = create_java_library_providers(
ctx,
library_output = outputs.classpath_entry if outputs else None,
declared_deps = ctx.attrs.deps + deps_query,
exported_deps = ctx.attrs.exported_deps,
provided_deps = ctx.attrs.provided_deps + provided_deps_query,
exported_provided_deps = ctx.attrs.exported_provided_deps,
runtime_deps = ctx.attrs.runtime_deps,
needs_desugar = source_level > 7 or target_level > 7,
generated_sources = generated_sources + [outputs.annotation_processor_output] if outputs and outputs.annotation_processor_output else [],
)
default_info = get_default_info(outputs, sub_targets)
return JavaProviders(
java_library_info = java_library_info,
java_library_intellij_info = intellij_info,
java_packaging_info = java_packaging_info,
shared_library_info = shared_library_info,
cxx_resource_info = cxx_resource_info,
template_placeholder_info = template_placeholder_info,
default_info = default_info,
)