# 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, )