451 lines
19 KiB
Python
451 lines
19 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//:resources.bzl",
|
|
"ResourceInfo",
|
|
"gather_resources",
|
|
)
|
|
load("@prelude//java:dex.bzl", "get_dex_produced_from_java_library")
|
|
load("@prelude//java:dex_toolchain.bzl", "DexToolchainInfo")
|
|
load("@prelude//java/utils:java_utils.bzl", "get_path_separator")
|
|
load(
|
|
"@prelude//linking:shared_libraries.bzl",
|
|
"SharedLibraryInfo",
|
|
"merge_shared_libraries",
|
|
)
|
|
load("@prelude//utils:utils.bzl", "expect")
|
|
|
|
# JAVA PROVIDER DOCS
|
|
#
|
|
# Our core Java provider is JavaLibraryInfo. At a basic level, this provider needs to give
|
|
# its dependents the ability to do two things: compilation and packaging.
|
|
#
|
|
# Compilation
|
|
#
|
|
# When we compile, we need to add all of our dependencies to the classpath. That includes
|
|
# anything in `deps`, `exported_deps`, `provided_deps` and `exported_provided_deps`, (but
|
|
# not `runtime_deps`). Additionally, it includes anything that these dependencies export
|
|
# (via `exported_deps` or `exported_provided_deps`). For example, if A depends upon B,
|
|
# and B has an exported dependency on C, then we need to add both B and C to the classpath
|
|
# when compiling A, i.e. both B and C need to be part of B's `compiling_deps`.
|
|
#
|
|
# Therefore, the `compiling_deps` consist of the library's own output (if it exists) plus
|
|
# the `compiling_deps` of any `exported_deps` and `exported_provided_deps`.
|
|
#
|
|
# When we compile, we don't need to compile against the full library - instead, we just
|
|
# compile against the library's public interface, or ABI.
|
|
#
|
|
# Packaging
|
|
#
|
|
# When we package our Java code into a `java_binary`, we need to include all of the Java
|
|
# code that is need to run the application - i.e. all the transitive dependencies. That
|
|
# includes anything in `deps`, `exported_deps` and `runtime_deps` (but not `provided_deps`
|
|
# or `exported_provided_deps`). For example, if A depends upon B, and B has a `dep` on C
|
|
# and a `provided_dep` on D, then if we package A we also need to include B and C, but
|
|
# not D.
|
|
#
|
|
# Therefore, the `packaging_deps` consist of the library's own output (if it exists) plus
|
|
# the `packaging_deps` of any `deps`, `exported_deps` and `runtime_deps`.
|
|
#
|
|
# When we package, we need to use the full library (since we are actually going to be
|
|
# running the code contained in the library).
|
|
#
|
|
# We also need to package up any native code that is declared transitively. The
|
|
# `SharedLibraryInfo` also consists of the `SharedLibraryInfo` of any `deps`,
|
|
# `exported_deps` and `runtime_deps`.
|
|
#
|
|
# Android
|
|
#
|
|
# Because Android uses Java, and we don't currently have the ability to "overlay" our
|
|
# providers, the core Java providers are extended to support Android's requirements.
|
|
# This introduces some additional complexity.
|
|
#
|
|
# Android doesn't package Java bytecode, but instead it converts the Java bytecode
|
|
# .dex (Dalvik Executable) files that are packaged into the Android binary (either an
|
|
# APK or an AAB). Therefore, our `packaging_deps` contain not just a `jar` field but
|
|
# also a `dex` field. If the `dex` field is empty, then the dep should not be
|
|
# packaged into the APK - this is useful for things like `android_build_config` where
|
|
# we want the output (.jar) to be present in any Java annotation processors that we
|
|
# run, but not in the final binary (since we rewrite the build config at the binary
|
|
# level anyway).
|
|
#
|
|
# Android also provides the ability to run Proguard on your binary in order to
|
|
# remove unused classes etc. Each Java library can specify any classes that it wants
|
|
# to always keep etc, via a `proguard_config`. This config also needs to be added to
|
|
# the `packaging_deps`.
|
|
#
|
|
# Java-like rules also provide a "special" function that can be used inside queries:
|
|
# "classpath". `classpath(A)` returns all of the packaging deps of A, while
|
|
# `classpath(A, 1)` returns all of the first-order packaging deps of A.
|
|
|
|
JavaClasspathEntry = record(
|
|
full_library = field("artifact"),
|
|
abi = field("artifact"),
|
|
required_for_source_only_abi = field(bool.type),
|
|
)
|
|
|
|
def _args_for_ast_dumper(entry: JavaClasspathEntry.type):
|
|
return [
|
|
"--dependency",
|
|
'"{}"'.format(entry.abi.owner),
|
|
entry.abi,
|
|
]
|
|
|
|
def _args_for_compiling(entry: JavaClasspathEntry.type):
|
|
return entry.abi
|
|
|
|
def _javacd_json(v):
|
|
return struct(path = v.abi)
|
|
|
|
JavaCompilingDepsTSet = transitive_set(
|
|
args_projections = {
|
|
"args_for_ast_dumper": _args_for_ast_dumper,
|
|
"args_for_compiling": _args_for_compiling,
|
|
},
|
|
json_projections = {
|
|
"javacd_json": _javacd_json,
|
|
},
|
|
)
|
|
|
|
JavaPackagingDep = record(
|
|
label = "label",
|
|
jar = ["artifact", None],
|
|
dex = ["DexLibraryInfo", None],
|
|
is_prebuilt_jar = bool.type,
|
|
proguard_config = ["artifact", None],
|
|
|
|
# An output that is used solely by the system to have an artifact bound to the target (that the core can then use to find
|
|
# the right target from the given artifact).
|
|
output_for_classpath_macro = "artifact",
|
|
)
|
|
|
|
def _full_jar_args(dep: JavaPackagingDep.type):
|
|
if dep.jar:
|
|
return [dep.jar]
|
|
return []
|
|
|
|
def _args_for_classpath_macro(dep: JavaPackagingDep.type):
|
|
return dep.output_for_classpath_macro
|
|
|
|
def _packaging_dep_javacd_json(dep: JavaPackagingDep.type):
|
|
if dep.jar:
|
|
return struct(path = dep.jar)
|
|
|
|
return struct()
|
|
|
|
JavaPackagingDepTSet = transitive_set(
|
|
args_projections = {
|
|
"args_for_classpath_macro": _args_for_classpath_macro,
|
|
"full_jar_args": _full_jar_args,
|
|
},
|
|
json_projections = {
|
|
"javacd_json": _packaging_dep_javacd_json,
|
|
},
|
|
)
|
|
|
|
JavaLibraryInfo = provider(
|
|
"Information about a java library and its dependencies",
|
|
fields = [
|
|
# Java dependencies exposed to dependent targets and supposed to be used during compilation.
|
|
# Consisting of this library's own output, and the "compiling_deps" of any exported_deps and exported_provided_deps.
|
|
#
|
|
"compiling_deps", # ["JavaCompilingDepsTSet", None]
|
|
|
|
# An output of the library. If present then already included into `compiling_deps` field.
|
|
"library_output", # ["JavaClasspathEntry", None]
|
|
|
|
# An output that is used solely by the system to have an artifact bound to the target (that the core can then use to find
|
|
# the right target from the given artifact).
|
|
"output_for_classpath_macro", # "artifact"
|
|
],
|
|
)
|
|
|
|
JavaLibraryIntellijInfo = provider(
|
|
"Information about a java library that is required for Intellij project generation",
|
|
fields = [
|
|
# All the artifacts that were used in order to compile this library
|
|
"compiling_classpath", # ["artifact"]
|
|
"generated_sources", # ["artifact"]
|
|
# Directory containing external annotation jars
|
|
"annotation_jars_dir", # ["artifact", None]
|
|
],
|
|
)
|
|
|
|
JavaPackagingInfo = provider(
|
|
fields = [
|
|
# Presents all java dependencies used to build this library and it's dependencies (all transitive deps except provided ones).
|
|
# These deps must be included into the final artifact.
|
|
"packaging_deps", # ["JavaPackagingDepTSet", None],
|
|
],
|
|
)
|
|
|
|
KeystoreInfo = provider(
|
|
fields = [
|
|
"store", # artifact
|
|
"properties", # artifact
|
|
],
|
|
)
|
|
|
|
JavaCompileOutputs = record(
|
|
full_library = "artifact",
|
|
class_abi = ["artifact", None],
|
|
source_abi = ["artifact", None],
|
|
source_only_abi = ["artifact", None],
|
|
classpath_entry = JavaClasspathEntry.type,
|
|
annotation_processor_output = ["artifact", None],
|
|
)
|
|
|
|
JavaProviders = record(
|
|
java_library_info = JavaLibraryInfo.type,
|
|
java_library_intellij_info = JavaLibraryIntellijInfo.type,
|
|
java_packaging_info = JavaPackagingInfo.type,
|
|
shared_library_info = SharedLibraryInfo.type,
|
|
cxx_resource_info = ResourceInfo.type,
|
|
template_placeholder_info = TemplatePlaceholderInfo.type,
|
|
default_info = DefaultInfo.type,
|
|
)
|
|
|
|
def to_list(java_providers: JavaProviders.type) -> ["provider"]:
|
|
return [
|
|
java_providers.java_library_info,
|
|
java_providers.java_library_intellij_info,
|
|
java_providers.java_packaging_info,
|
|
java_providers.shared_library_info,
|
|
java_providers.cxx_resource_info,
|
|
java_providers.template_placeholder_info,
|
|
java_providers.default_info,
|
|
]
|
|
|
|
# Creates a JavaCompileOutputs. `classpath_abi` can be set to specify a
|
|
# specific artifact to be used as the abi for the JavaClasspathEntry.
|
|
def make_compile_outputs(
|
|
full_library: "artifact",
|
|
class_abi: ["artifact", None] = None,
|
|
source_abi: ["artifact", None] = None,
|
|
source_only_abi: ["artifact", None] = None,
|
|
classpath_abi: ["artifact", None] = None,
|
|
required_for_source_only_abi: bool.type = False,
|
|
annotation_processor_output: ["artifact", None] = None) -> JavaCompileOutputs.type:
|
|
return JavaCompileOutputs(
|
|
full_library = full_library,
|
|
class_abi = class_abi,
|
|
source_abi = source_abi,
|
|
source_only_abi = source_only_abi,
|
|
classpath_entry = JavaClasspathEntry(
|
|
full_library = full_library,
|
|
abi = classpath_abi or class_abi or full_library,
|
|
required_for_source_only_abi = required_for_source_only_abi,
|
|
),
|
|
annotation_processor_output = annotation_processor_output,
|
|
)
|
|
|
|
def create_abi(actions: "actions", class_abi_generator: "dependency", library: "artifact") -> "artifact":
|
|
# It's possible for the library to be created in a subdir that is
|
|
# itself some actions output artifact, so we replace directory
|
|
# separators to get a path that we can uniquely own.
|
|
# TODO(cjhopman): This probably should take in the output path.
|
|
class_abi = actions.declare_output("{}-class-abi.jar".format(library.short_path.replace("/", "_")))
|
|
actions.run(
|
|
[
|
|
class_abi_generator[RunInfo],
|
|
library,
|
|
class_abi.as_output(),
|
|
],
|
|
category = "class_abi_generation",
|
|
identifier = library.short_path,
|
|
)
|
|
return class_abi
|
|
|
|
# Accumulate deps necessary for compilation, which consist of this library's output and compiling_deps of its exported deps
|
|
def derive_compiling_deps(
|
|
actions: "actions",
|
|
library_output: [JavaClasspathEntry.type, None],
|
|
children: ["dependency"]) -> ["JavaCompilingDepsTSet", None]:
|
|
if children:
|
|
filtered_children = filter(
|
|
None,
|
|
[exported_dep.compiling_deps for exported_dep in filter(None, [x.get(JavaLibraryInfo) for x in children])],
|
|
)
|
|
children = filtered_children
|
|
|
|
if not library_output and not children:
|
|
return None
|
|
|
|
if library_output:
|
|
return actions.tset(JavaCompilingDepsTSet, children = children, value = library_output)
|
|
else:
|
|
return actions.tset(JavaCompilingDepsTSet, children = children)
|
|
|
|
def create_java_packaging_dep(
|
|
ctx: "context",
|
|
library_jar: ["artifact", None] = None,
|
|
output_for_classpath_macro: ["artifact", None] = None,
|
|
needs_desugar: bool.type = False,
|
|
desugar_deps: ["artifact"] = [],
|
|
is_prebuilt_jar: bool.type = False,
|
|
dex_weight_factor: int.type = 1) -> "JavaPackagingDep":
|
|
dex_toolchain = getattr(ctx.attrs, "_dex_toolchain", None)
|
|
if library_jar != None and dex_toolchain != None and ctx.attrs._dex_toolchain[DexToolchainInfo].d8_command != None:
|
|
dex = get_dex_produced_from_java_library(
|
|
ctx,
|
|
ctx.attrs._dex_toolchain[DexToolchainInfo],
|
|
library_jar,
|
|
needs_desugar,
|
|
desugar_deps,
|
|
dex_weight_factor,
|
|
)
|
|
else:
|
|
dex = None
|
|
|
|
expect(library_jar != None or output_for_classpath_macro != None, "Must provide an output_for_classpath_macro if no library_jar is provided!")
|
|
|
|
return JavaPackagingDep(
|
|
label = ctx.label,
|
|
jar = library_jar,
|
|
dex = dex,
|
|
is_prebuilt_jar = is_prebuilt_jar,
|
|
proguard_config = getattr(ctx.attrs, "proguard_config", None),
|
|
output_for_classpath_macro = output_for_classpath_macro or library_jar,
|
|
)
|
|
|
|
def get_all_java_packaging_deps(ctx: "context", deps: ["dependency"]) -> ["JavaPackagingDep"]:
|
|
return get_all_java_packaging_deps_from_packaging_infos(ctx, filter(None, [x.get(JavaPackagingInfo) for x in deps]))
|
|
|
|
def get_all_java_packaging_deps_from_packaging_infos(ctx: "context", infos: ["JavaPackagingInfo"]) -> ["JavaPackagingDep"]:
|
|
children = filter(None, [info.packaging_deps for info in infos])
|
|
if not children:
|
|
return []
|
|
|
|
tset = ctx.actions.tset(JavaPackagingDepTSet, children = children)
|
|
|
|
return list(tset.traverse())
|
|
|
|
def get_all_java_packaging_deps_tset(
|
|
ctx: "context",
|
|
java_packaging_infos: ["JavaPackagingInfo"],
|
|
java_packaging_dep: [JavaPackagingDep.type, None] = None) -> [JavaPackagingDepTSet.type, None]:
|
|
packaging_deps_kwargs = {}
|
|
if java_packaging_dep:
|
|
packaging_deps_kwargs["value"] = java_packaging_dep
|
|
|
|
packaging_deps_children = filter(None, [info.packaging_deps for info in java_packaging_infos])
|
|
if packaging_deps_children:
|
|
packaging_deps_kwargs["children"] = packaging_deps_children
|
|
|
|
return ctx.actions.tset(JavaPackagingDepTSet, **packaging_deps_kwargs) if packaging_deps_kwargs else None
|
|
|
|
# Accumulate deps necessary for packaging, which consist of all transitive java deps (except provided ones)
|
|
def get_java_packaging_info(
|
|
ctx: "context",
|
|
raw_deps: ["dependency"],
|
|
java_packaging_dep: [JavaPackagingDep.type, None] = None) -> JavaPackagingInfo.type:
|
|
java_packaging_infos = filter(None, [x.get(JavaPackagingInfo) for x in raw_deps])
|
|
packaging_deps = get_all_java_packaging_deps_tset(ctx, java_packaging_infos, java_packaging_dep)
|
|
return JavaPackagingInfo(packaging_deps = packaging_deps)
|
|
|
|
def create_native_providers(actions: "actions", label: "label", packaging_deps: ["dependency"]) -> (SharedLibraryInfo.type, ResourceInfo.type):
|
|
shared_library_info = merge_shared_libraries(
|
|
actions,
|
|
deps = filter(None, [x.get(SharedLibraryInfo) for x in packaging_deps]),
|
|
)
|
|
cxx_resource_info = ResourceInfo(resources = gather_resources(
|
|
label,
|
|
deps = packaging_deps,
|
|
))
|
|
return shared_library_info, cxx_resource_info
|
|
|
|
def _create_non_template_providers(
|
|
ctx: "context",
|
|
library_output: [JavaClasspathEntry.type, None],
|
|
declared_deps: ["dependency"] = [],
|
|
exported_deps: ["dependency"] = [],
|
|
exported_provided_deps: ["dependency"] = [],
|
|
runtime_deps: ["dependency"] = [],
|
|
needs_desugar: bool.type = False,
|
|
desugar_classpath: ["artifact"] = [],
|
|
is_prebuilt_jar: bool.type = False) -> (JavaLibraryInfo.type, JavaPackagingInfo.type, SharedLibraryInfo.type, ResourceInfo.type):
|
|
"""Creates java library providers of type `JavaLibraryInfo` and `JavaPackagingInfo`.
|
|
|
|
Args:
|
|
library_output: optional JavaClasspathEntry that represents library output
|
|
declared_deps: declared dependencies (usually comes from `deps` field of the rule)
|
|
exported_deps: dependencies that are exposed to dependent rules as compiling deps
|
|
exported_provided_deps: dependencies that are are exposed to dependent rules and not be included into packaging
|
|
runtime_deps: dependencies that are used for packaging only
|
|
"""
|
|
packaging_deps = declared_deps + exported_deps + runtime_deps
|
|
shared_library_info, cxx_resource_info = create_native_providers(ctx.actions, ctx.label, packaging_deps)
|
|
|
|
output_for_classpath_macro = library_output.abi if (library_output and library_output.abi.owner != None) else ctx.actions.write("dummy_output_for_classpath_macro.txt", "Unused")
|
|
java_packaging_dep = create_java_packaging_dep(ctx, library_output.full_library if library_output else None, output_for_classpath_macro, needs_desugar, desugar_classpath, is_prebuilt_jar)
|
|
|
|
java_packaging_info = get_java_packaging_info(
|
|
ctx,
|
|
raw_deps = packaging_deps,
|
|
java_packaging_dep = java_packaging_dep,
|
|
)
|
|
|
|
return (
|
|
JavaLibraryInfo(
|
|
compiling_deps = derive_compiling_deps(ctx.actions, library_output, exported_deps + exported_provided_deps),
|
|
library_output = library_output,
|
|
output_for_classpath_macro = output_for_classpath_macro,
|
|
),
|
|
java_packaging_info,
|
|
shared_library_info,
|
|
cxx_resource_info,
|
|
)
|
|
|
|
def create_template_info(packaging_info: JavaPackagingInfo.type, first_order_classpath_libs: ["artifact"]) -> TemplatePlaceholderInfo.type:
|
|
return TemplatePlaceholderInfo(keyed_variables = {
|
|
"classpath": cmd_args(packaging_info.packaging_deps.project_as_args("full_jar_args"), delimiter = get_path_separator()) if packaging_info.packaging_deps else cmd_args(),
|
|
"classpath_including_targets_with_no_output": cmd_args(packaging_info.packaging_deps.project_as_args("args_for_classpath_macro"), delimiter = get_path_separator()),
|
|
"first_order_classpath": cmd_args(first_order_classpath_libs, delimiter = get_path_separator()),
|
|
})
|
|
|
|
def create_java_library_providers(
|
|
ctx: "context",
|
|
library_output: [JavaClasspathEntry.type, None],
|
|
declared_deps: ["dependency"] = [],
|
|
exported_deps: ["dependency"] = [],
|
|
provided_deps: ["dependency"] = [],
|
|
exported_provided_deps: ["dependency"] = [],
|
|
runtime_deps: ["dependency"] = [],
|
|
needs_desugar: bool.type = False,
|
|
is_prebuilt_jar: bool.type = False,
|
|
generated_sources: ["artifact"] = [],
|
|
annotation_jars_dir: ["artifact", None] = None) -> (JavaLibraryInfo.type, JavaPackagingInfo.type, SharedLibraryInfo.type, ResourceInfo.type, TemplatePlaceholderInfo.type, JavaLibraryIntellijInfo.type):
|
|
first_order_classpath_deps = filter(None, [x.get(JavaLibraryInfo) for x in declared_deps + exported_deps + runtime_deps])
|
|
first_order_classpath_libs = [dep.output_for_classpath_macro for dep in first_order_classpath_deps]
|
|
|
|
compiling_deps = derive_compiling_deps(ctx.actions, None, declared_deps + exported_deps + provided_deps + exported_provided_deps)
|
|
compiling_classpath = [dep.full_library for dep in (list(compiling_deps.traverse()) if compiling_deps else [])]
|
|
desugar_classpath = compiling_classpath if needs_desugar else []
|
|
|
|
library_info, packaging_info, shared_library_info, cxx_resource_info = _create_non_template_providers(
|
|
ctx,
|
|
library_output = library_output,
|
|
declared_deps = declared_deps,
|
|
exported_deps = exported_deps,
|
|
exported_provided_deps = exported_provided_deps,
|
|
runtime_deps = runtime_deps,
|
|
needs_desugar = needs_desugar,
|
|
desugar_classpath = desugar_classpath,
|
|
is_prebuilt_jar = is_prebuilt_jar,
|
|
)
|
|
|
|
first_order_libs = first_order_classpath_libs + [library_info.library_output.full_library] if library_info.library_output else first_order_classpath_libs
|
|
template_info = create_template_info(packaging_info, first_order_libs)
|
|
|
|
intellij_info = JavaLibraryIntellijInfo(
|
|
compiling_classpath = compiling_classpath,
|
|
generated_sources = generated_sources,
|
|
annotation_jars_dir = annotation_jars_dir,
|
|
)
|
|
|
|
return (library_info, packaging_info, shared_library_info, cxx_resource_info, template_info, intellij_info)
|