blob: 2676eb36ee11d3d4237a43847f141e173653d1de [file] [log] [blame]
"""Aspects to build and collect project dependencies."""
# Load external dependencies of this aspect. These are loaded in a separate file and re-exported as necessary
# to make supporting other versions of bazel easier, by replacing build_dependencies_deps.bzl.
load(
":build_dependencies_deps.bzl",
"ZIP_TOOL_LABEL",
_ide_android_not_validated = "IDE_ANDROID",
_ide_cc_not_validated = "IDE_CC",
_ide_java_not_validated = "IDE_JAVA",
_ide_java_proto_not_validated = "IDE_JAVA_PROTO",
_ide_kotlin_not_validated = "IDE_KOTLIN",
)
def _rule_function(
rule): # @unused
return []
def _target_rule_function(
target, # @unused
rule): # @unused
return []
def _unique(values):
return {k: None for k in values}.keys()
def _validate_ide(unvalidated, template):
"Basic validation that a provided implementation conforms to a given template"
for a in dir(template):
if not hasattr(unvalidated, a):
fail("attribute missing: ", a, unvalidated)
elif type(getattr(unvalidated, a)) != type(getattr(template, a)):
fail("attribute type mismatch: ", a, type(getattr(unvalidated, a)), type(getattr(template, a)))
return struct(**{a: getattr(unvalidated, a) for a in dir(template) if a not in dir(struct())})
IDE_JAVA = _validate_ide(
_ide_java_not_validated,
template = struct(
srcs_attributes = [], # Additional srcs like attributes.
get_java_info = _target_rule_function, # A function that takes a rule and returns a JavaInfo like structure (or the provider itself).
),
)
IDE_ANDROID = _validate_ide(
_ide_android_not_validated,
template = struct(
get_android_info = _target_rule_function, # A function that takes a rule and returns a JavaInfo like structure (or the provider itself).
),
)
IDE_KOTLIN = _validate_ide(
_ide_kotlin_not_validated,
template = struct(
srcs_attributes = [], # Additional srcs like attributes.
follow_attributes = [], # Additional attributes for the aspect to follow and request DependenciesInfo provider.
follow_additional_attributes = [], # Additional attributes for the aspect to follow without requesting DependenciesInfo provider.
followed_dependencies = _rule_function, # A function that takes a rule and returns a list of dependencies (targets or toolchain containers).
toolchains_aspects = [], # Toolchain types for the aspect to follow.
get_kotlin_info = _target_rule_function, # A function that takes a rule and returns a marker struct if the target
# was recognised as a Kotlin related target and `followed_dependenices` must be called.
),
)
IDE_JAVA_PROTO = _validate_ide(
_ide_java_proto_not_validated,
template = struct(
get_java_proto_info = _target_rule_function, # A function that takes a rule and returns a marker structure (empty for now).
srcs_attributes = [], # Additional srcs like attributes.
follow_attributes = [], # Additional attributes for the aspect to follow and request DependenciesInfo provider.
followed_dependencies = _rule_function, # A function that takes a rule and returns a list of dependencies (targets or toolchain containers).
toolchains_aspects = [], # Toolchain types for the aspect to follow.
),
)
IDE_CC = _validate_ide(
_ide_cc_not_validated,
template = struct(
follow_attributes = ["_cc_toolchain"], # Additional attributes for the aspect to follow and request DependenciesInfo provider.
toolchains_aspects = [], # Toolchain types for the aspect to follow.
toolchain_target = _rule_function, # A function that takes a rule and returns a toolchain target (or a toolchain container).
compilation_context = _rule_function, # A function that takes a target and returns compilation_context of target.
cc_toolchain_info = _rule_function, # A function that takes a target and ctx, returns a struct that contains cc toolchain info.
),
)
JVM_SRC_ATTRS = _unique(["srcs"] + IDE_JAVA.srcs_attributes + IDE_JAVA_PROTO.srcs_attributes + IDE_KOTLIN.srcs_attributes)
def _noneToEmpty(d):
return d if d else depset()
def _package_dependencies_impl(target, ctx):
dep_info = target[DependenciesInfo]
java_info = IDE_JAVA.get_java_info(target, ctx.rule)
java_info_files = _noneToEmpty(dep_info.java_info_files)
cc_info_files = _noneToEmpty(dep_info.cc_info_files)
return [OutputGroupInfo(
qs_jars = _noneToEmpty(dep_info.compile_time_jars),
qs_transitive_runtime_jars = java_info.transitive_runtime_jars_depset if java_info else depset(),
qs_info = java_info_files,
qs_jdeps = _noneToEmpty(dep_info.compile_jdeps),
qs_aars = _noneToEmpty(dep_info.aars),
qs_gensrcs = _noneToEmpty(dep_info.gensrcs),
qs_cc_headers = _noneToEmpty(dep_info.cc_headers),
qs_cc_info = cc_info_files,
)]
def _write_java_target_info(ctx, label, target_info):
"""Write java target info to a file in proto format.
The proto format used is defined by proto bazel.intellij.JavaArtifacts.
"""
file_name = label.name + ".java-info.txt"
artifact_info_file = ctx.actions.declare_file(file_name)
ctx.actions.write(
artifact_info_file,
_encode_target_info_proto(target_info),
)
return artifact_info_file
def _write_cc_target_info(label, cc_compilation_info, ctx):
"""Write CC target info to a file in proto format.
The proto format used defined by proto bazel.intellij.CcCompilationInfo.
"""
cc_info_file_name = label.name + ".cc-info.txt"
cc_info_file = ctx.actions.declare_file(cc_info_file_name)
ctx.actions.write(
cc_info_file,
_encode_cc_compilation_info_proto(label, cc_compilation_info),
)
return cc_info_file
DependenciesInfo = provider(
"The out-of-project dependencies",
fields = {
"label": "the label of a target it describes",
"java_info_file": "a file containing java related info",
"java_info_files": "a list of java info files",
"compile_time_jars": "a list of jars generated by targets",
"compile_jdeps": "a list of jdeps files desribing targets",
"aars": "a list of aars with resource files",
"gensrcs": "a list of sources generated by project targets",
"expand_sources": "boolean, true if the sources for this target should be expanded when it appears inside another rules srcs list",
"cc_info_files": "a list of cc info files",
"cc_compilation_info": "a structure containing info required to compile cc sources",
"cc_headers": "a depset of generated headers required to compile cc sources",
"cc_toolchain_info": "struct containing cc toolchain info, with keys file (the output file) and id (unique ID for the toolchain info, referred to from elsewhere)",
},
)
def create_dependencies_info(
label,
java_info_file = None,
java_info_files = depset(),
compile_time_jars = depset(),
compile_jdeps = depset(),
aars = depset(),
gensrcs = depset(),
expand_sources = False,
cc_info_files = depset(),
cc_compilation_info = None,
cc_headers = depset(),
cc_toolchain_info = None):
"""A helper function to create a DependenciesInfo provider instance."""
return DependenciesInfo(
label = label,
java_info_file = java_info_file,
java_info_files = java_info_files,
compile_time_jars = compile_time_jars,
compile_jdeps = compile_jdeps,
aars = aars,
gensrcs = gensrcs,
expand_sources = expand_sources,
cc_info_files = cc_info_files,
cc_compilation_info = cc_compilation_info,
cc_headers = cc_headers,
cc_toolchain_info = cc_toolchain_info,
)
def create_java_dependencies_info(
info_file,
info_files,
compile_time_jars,
compile_jdeps,
aars,
gensrcs,
expand_sources):
"""A helper function to create a DependenciesInfo provider instance."""
return struct(
info_file = info_file,
info_files = info_files,
compile_time_jars = compile_time_jars,
compile_jdeps = compile_jdeps,
aars = aars,
gensrcs = gensrcs,
expand_sources = expand_sources,
)
def create_cc_dependencies_info(
cc_info_files = depset(),
cc_compilation_info = None,
cc_headers = depset(),
cc_toolchain_info = None):
"""A helper function to create a DependenciesInfo provider instance."""
return struct(
cc_info_files = cc_info_files,
cc_compilation_info = cc_compilation_info,
cc_headers = cc_headers,
cc_toolchain_info = cc_toolchain_info,
)
def create_cc_toolchain_info(
cc_toolchain_info = None):
"""A helper function to create a DependenciesInfo provider instance."""
return struct(
cc_toolchain_info = cc_toolchain_info,
)
def merge_dependencies_info(
target,
ctx, # @unused
java_dep_info,
cc_dep_info,
cc_toolchain_dep_info):
"""Merge multiple DependenciesInfo providers into one.
Depsets and dicts are merged. For members such as `cc_compilation_info`, we require that at most one of the
DependenciesInfo's defines this which should always be the case.
Args:
target: the target.
ctx: the context which is ignored in this function.
java_dep_info: java dep info.
cc_dep_info: cc dep info.
cc_toolchain_dep_info: cc toolchain dep info.
Returns:
Merged dependencies info.
"""
if not java_dep_info and not cc_dep_info and not cc_toolchain_dep_info:
return create_dependencies_info(label = target.label)
merged = create_dependencies_info(
label = target.label,
java_info_file = java_dep_info.info_file if java_dep_info else None,
java_info_files = java_dep_info.info_files if java_dep_info else None,
compile_time_jars = java_dep_info.compile_time_jars if java_dep_info else None,
compile_jdeps = java_dep_info.compile_jdeps if java_dep_info else None,
aars = java_dep_info.aars if java_dep_info else None,
gensrcs = java_dep_info.gensrcs if java_dep_info else None,
expand_sources = java_dep_info.expand_sources if java_dep_info else None,
cc_info_files = cc_dep_info.cc_info_files if cc_dep_info else None,
cc_compilation_info = cc_dep_info.cc_compilation_info if cc_dep_info else None,
cc_headers = cc_dep_info.cc_headers if cc_dep_info else None,
cc_toolchain_info = cc_toolchain_dep_info.cc_toolchain_info if cc_toolchain_dep_info else None,
)
return merged
def one_of(a, b):
"""
Returns whichever of a or b is not None, None if both are, or fails if neither are.
Args:
a: maybe None argument.
b: maybe None argument.
Returns:
Whichever of a or b is not None, None if both are, or fails if neither are.
"""
if a == None:
return b
if b == None:
return a
fail("Expected at most one, but got both", a, b)
def _encode_target_info_proto(target_info):
contents = struct(
target = target_info.target,
is_external_dependency = target_info.is_external_dependency,
dep_java_info_files = _encode_file_list(target_info.dep_java_info_files),
jars = _encode_file_list(target_info.jars),
compile_jdeps = _encode_file_list(target_info.compile_jdeps),
output_jars = _encode_file_list(target_info.output_jars),
ide_aars = _encode_file_list(target_info.ide_aars),
gen_srcs = _encode_file_list(target_info.gen_srcs),
srcs = target_info.srcs,
srcjars = target_info.srcjars,
android_resources_package = target_info.android_resources_package,
)
return proto.encode_text(contents)
def _encode_file_list(files):
"""Encodes a list of files as a struct.
Returns:
A list fo structs matching the bazel.intellij.OutputArtifact proto message.
"""
r = []
for f in files:
if f.is_directory:
r.append(struct(directory = f.path))
else:
r.append(struct(file = f.path))
return r
def _encode_cc_compilation_info_proto(label, cc_compilation_info):
return proto.encode_text(
struct(targets = [
struct(
label = str(label),
defines = cc_compilation_info.transitive_defines,
include_directories = cc_compilation_info.transitive_include_directory,
quote_include_directories = cc_compilation_info.transitive_quote_include_directory,
system_include_directories = cc_compilation_info.transitive_system_include_directory,
framework_include_directories = cc_compilation_info.framework_include_directory,
gen_hdrs = _encode_file_list(cc_compilation_info.gen_headers),
toolchain_id = cc_compilation_info.toolchain_id,
),
]),
)
# Do not remove parameters. They may be used to configure experiment values.
#buildifier: disable=unused_parameters
def package_dependencies(unused_parameters):
return aspect(
implementation = _package_dependencies_impl,
required_aspect_providers = [[DependenciesInfo]],
)
def declares_android_resources(target, ctx):
"""
Returns true if the target has resource files and an android provider.
The IDE needs aars from targets that declare resources. AndroidIdeInfo
has a defined_android_resources flag, but this returns true for additional
cases (aidl files, etc), so we check if the target has resource files.
Args:
target: the target.
ctx: the context.
Returns:
True if the target has resource files and an android provider.
"""
if IDE_ANDROID.get_android_info(target, ctx.rule) == None:
return False
return hasattr(ctx.rule.attr, "resource_files") and len(ctx.rule.attr.resource_files) > 0
def declares_aar_import(ctx):
"""
Returns true if the target has aar and is aar_import rule.
Args:
ctx: the context.
Returns:
True if the target has aar and is aar_import rule.
"""
return ctx.rule.kind == "aar_import" and hasattr(ctx.rule.attr, "aar")
def _collect_dependencies_impl(target, ctx, params):
return _collect_dependencies_core_impl(
target,
ctx,
params,
)
def _package_prefix_match(package, prefix):
if (package == prefix):
return True
if package.startswith(prefix) and package[len(prefix)] == "/":
return True
return False
def _get_repo_name(label):
# The API to get the repo name changed between bazel versions. Use whichever exists:
return label.repo_name if "repo_name" in dir(label) else label.workspace_name
def _target_within_project_scope(label, include, exclude):
repo = _get_repo_name(label)
package = label.package
result = False
if include:
if len(include) == 1 and include[0] == "//":
# when workspace root is included
result = True
else:
for inc in [Label(i) for i in include]:
if _get_repo_name(inc) == repo and _package_prefix_match(package, inc.package):
result = True
break
if result and len(exclude) > 0:
for exc in [Label(i) for i in exclude]:
if _get_repo_name(exc) == repo and _package_prefix_match(package, exc.package):
result = False
break
return result
def _get_dependency_attribute(rule, attr):
if hasattr(rule.attr, attr):
to_add = getattr(rule.attr, attr)
if type(to_add) == "list":
return [t for t in to_add if type(t) == "Target"]
elif type(to_add) == "Target":
return [to_add]
return []
def _get_followed_java_dependency_infos(
label, # @unused
rule):
deps = []
for attr in FOLLOW_JAVA_ATTRIBUTES:
deps.extend(_get_dependency_attribute(rule, attr))
deps.extend(IDE_JAVA_PROTO.followed_dependencies(rule))
deps.extend(IDE_KOTLIN.followed_dependencies(rule))
return {
str(dep[DependenciesInfo].label): dep[DependenciesInfo] # NOTE: This handles duplicates.
for dep in deps
if DependenciesInfo in dep and dep[DependenciesInfo].java_info_files
}
def _collect_own_java_artifacts(
target,
ctx,
always_build_rules,
generate_aidl_classes,
use_generated_srcjars,
target_is_within_project_scope):
rule = ctx.rule
must_build_main_artifacts = (
not target_is_within_project_scope or rule.kind in always_build_rules
)
own_jar_files = []
own_jar_depsets = []
own_compile_jdeps_files = []
own_output_jar_files = []
own_ide_aar_files = []
own_gensrc_files = []
own_src_file_paths = []
own_srcjar_file_paths = []
resource_package = ""
java_info = IDE_JAVA.get_java_info(target, ctx.rule)
kotlin_info = IDE_KOTLIN.get_kotlin_info(target, ctx.rule)
# Targets recognised as java_proto_info can have java_info dependencies.
java_proto_info = IDE_JAVA_PROTO.get_java_proto_info(target, ctx.rule)
android_info = IDE_ANDROID.get_android_info(target, ctx.rule)
if must_build_main_artifacts:
# For rules that we do not follow dependencies of (either because they don't
# have further dependencies with JavaInfo or do so in attributes we don't care)
# we gather all their transitive dependencies. If they have dependencies, we
# only gather their own compile jars and continue down the tree.
# This is done primarily for rules like proto, whose toolchain classes
# are collected via attribute traversal, but still requires jars for any
# proto deps of the underlying proto_library.
if java_info:
for generated_output in java_info.generated_outputs:
if generated_output.compile_jdeps:
own_compile_jdeps_files.append(generated_output.compile_jdeps)
own_jar_depsets.append(java_info.compile_jars_depset)
own_output_jar_files = java_info.java_output_compile_jars
if declares_android_resources(target, ctx):
ide_aar = _get_ide_aar_file(target, ctx)
if ide_aar:
# TODO(mathewi) - handle source aars
if not ide_aar.is_source:
own_ide_aar_files.append(ide_aar)
elif declares_aar_import(ctx):
ide_aar = rule.attr.aar.files.to_list()[0]
# TODO(mathewi) - handle source aars
if not ide_aar.is_source:
own_ide_aar_files.append(ide_aar)
else:
if android_info != None:
resource_package = android_info.java_package
if generate_aidl_classes:
add_base_idl_jar = False
idl_jar = android_info.idl_class_jar
if idl_jar != None:
own_jar_files.append(idl_jar)
add_base_idl_jar = True
generated_java_files = android_info.idl_generated_java_files
if generated_java_files:
own_gensrc_files += generated_java_files
add_base_idl_jar = True
# An AIDL base jar needed for resolving base classes for aidl generated stubs.
if add_base_idl_jar:
if hasattr(rule.attr, "_aidl_lib"):
own_jar_depsets.append(rule.attr._aidl_lib.files)
elif hasattr(rule.attr, "_android_sdk") and hasattr(android_common, "AndroidSdkInfo"):
android_sdk_info = getattr(rule.attr, "_android_sdk")[android_common.AndroidSdkInfo]
own_jar_depsets.append(android_sdk_info.aidl_lib.files)
# Add generated java_outputs (e.g. from annotation processing)
generated_class_jars = []
if java_info:
for generated_output in java_info.generated_outputs:
# Prefer source jars if they exist and are requested:
if use_generated_srcjars and generated_output.generated_source_jar:
own_gensrc_files.append(generated_output.generated_source_jar)
elif generated_output.generated_class_jar:
generated_class_jars.append(generated_output.generated_class_jar)
if generated_class_jars:
own_jar_files += generated_class_jars
# Add generated sources for included targets
for src_attr in JVM_SRC_ATTRS:
if hasattr(rule.attr, src_attr):
for src in getattr(rule.attr, src_attr):
# If the target that generates this source specifies that
# the sources should be expanded, we ignore the generated
# sources - the IDE will substitute the target sources
# themselves instead.
if not (DependenciesInfo in src and src[DependenciesInfo].expand_sources):
for file in src.files.to_list():
if not file.is_source:
own_gensrc_files.append(file)
if not target_is_within_project_scope:
for src_attr in JVM_SRC_ATTRS:
if hasattr(rule.attr, src_attr):
for src in getattr(rule.attr, src_attr):
for file in src.files.to_list():
if file.is_source:
own_src_file_paths.append(file.path)
else:
own_gensrc_files.append(file)
if hasattr(rule.attr, "srcjar"):
if rule.attr.srcjar and type(rule.attr.srcjar) == "Target":
for file in rule.attr.srcjar.files.to_list():
if file.is_source:
own_srcjar_file_paths.append(file.path)
else:
own_gensrc_files.append(file)
if not (java_info or kotlin_info or android_info or java_proto_info or own_gensrc_files or own_src_file_paths or own_srcjar_file_paths):
return None
if own_jar_files or len(own_jar_depsets) > 1:
own_jar_depset = depset(own_jar_files, transitive = own_jar_depsets)
# here two cases left: own_jar_files is None/[] and own_jar_depsets is None/[] or a singleton.
elif not own_jar_depsets:
own_jar_depset = depset()
elif len(own_jar_depsets) == 1:
own_jar_depset = own_jar_depsets[0]
else:
# See the comment above.
fail("Unexpected: " + str(own_jar_files) + " " + str(own_jar_depsets))
return struct(
is_external_dependency = must_build_main_artifacts,
jar_depset = own_jar_depset,
compile_jdeps_depset = depset(own_compile_jdeps_files),
output_jar_depset = depset(own_output_jar_files),
ide_aar_depset = depset(own_ide_aar_files),
gensrc_depset = depset(own_gensrc_files),
src_file_paths = own_src_file_paths,
srcjar_file_paths = own_srcjar_file_paths,
android_resources_package = resource_package,
)
def _target_to_artifact_entry(
label = "",
is_external_dependency = False,
dep_java_info_files = [],
jars = [],
compile_jdeps = [],
output_jars = [],
ide_aars = [],
gen_srcs = [],
srcs = [],
srcjars = [],
android_resources_package = ""):
return struct(
target = label,
is_external_dependency = is_external_dependency,
dep_java_info_files = dep_java_info_files,
jars = jars,
compile_jdeps = compile_jdeps,
output_jars = output_jars,
ide_aars = ide_aars,
gen_srcs = gen_srcs,
srcs = srcs,
srcjars = srcjars,
android_resources_package = android_resources_package,
)
# Collects artifacts exposed by this java-like (i.e. java, android or proto-for-java) target and its dependencies if it is such a target.
# For non-Java targets only generated sources are collected without recursing to its dependencies. Therefore, for example, if there are
# generated proto files they won't be collected and this use case will need to be supported explicitly. Not following non-Java dependencies
# while collecting Java info files substantially reduces the number of generated and fetched info files.
def _collect_own_and_dependency_java_artifacts(
target,
ctx,
dependency_infos,
always_build_rules,
generate_aidl_classes,
use_generated_srcjars,
target_is_within_project_scope):
own_files = _collect_own_java_artifacts(
target,
ctx,
always_build_rules,
generate_aidl_classes,
use_generated_srcjars,
target_is_within_project_scope,
)
if not own_files:
# Any target recognized as a java related target gets at least
# an empty own_files structure.
return None
target_to_artifacts = {}
# Flattening is fine here as these are files from a single target (maybe some are re-exported from a few of its depende3ncies).
dep_java_info_files = [info.java_info_file for info in dependency_infos.values() if info.java_info_file]
jars = own_files.jar_depset.to_list()
compile_jdeps = own_files.compile_jdeps_depset.to_list()
output_jars = own_files.output_jar_depset.to_list()
ide_aars = own_files.ide_aar_depset.to_list()
gen_srcs = own_files.gensrc_depset.to_list() # Flattening is fine here (these are files from one target)
java_info_file = _write_java_target_info(ctx, target.label, _target_to_artifact_entry(
label = str(target.label),
is_external_dependency = own_files.is_external_dependency,
dep_java_info_files = dep_java_info_files, # No flattening here. This is a list of direct dependencies only.
jars = jars,
compile_jdeps = compile_jdeps,
output_jars = output_jars,
ide_aars = ide_aars,
gen_srcs = gen_srcs,
srcs = own_files.src_file_paths,
srcjars = own_files.srcjar_file_paths,
android_resources_package = own_files.android_resources_package,
))
own_and_transitive_compile_jdeps_depsets = [own_files.compile_jdeps_depset]
own_and_transitive_jar_depsets = [own_files.jar_depset, own_files.output_jar_depset] # Copy to prevent changes to own_files.jar_depset.
own_and_transitive_ide_aar_depsets = []
own_and_transitive_gensrc_depsets = []
for info in dependency_infos.values():
own_and_transitive_jar_depsets.append(info.compile_time_jars)
own_and_transitive_compile_jdeps_depsets.append(info.compile_jdeps)
own_and_transitive_ide_aar_depsets.append(info.aars)
own_and_transitive_gensrc_depsets.append(info.gensrcs)
return struct(
java_info_file = java_info_file,
compile_jars = depset(transitive = own_and_transitive_jar_depsets),
compile_jdeps = depset(transitive = own_and_transitive_compile_jdeps_depsets),
aars = depset(ide_aars, transitive = own_and_transitive_ide_aar_depsets),
gensrcs = depset(gen_srcs, transitive = own_and_transitive_gensrc_depsets),
)
def _get_cc_toolchain_dependency_info(rule):
cc_toolchain_target = IDE_CC.toolchain_target(rule)
if cc_toolchain_target and DependenciesInfo in cc_toolchain_target:
return cc_toolchain_target[DependenciesInfo]
return None
def _collect_own_and_dependency_cc_info(target, rule):
dependency_info = _get_cc_toolchain_dependency_info(rule)
compilation_context = IDE_CC.compilation_context(target)
cc_toolchain_info = None
if dependency_info:
cc_toolchain_info = dependency_info.cc_toolchain_info
gen_headers = depset()
compilation_info = None
if compilation_context:
gen_headers = depset([f for f in compilation_context.headers.to_list() if not f.is_source])
compilation_info = struct(
transitive_defines = compilation_context.defines.to_list(),
transitive_include_directory = compilation_context.includes.to_list(),
transitive_quote_include_directory = compilation_context.quote_includes.to_list(),
transitive_system_include_directory = (
compilation_context.system_includes.to_list() + (
# external_includes was added in newer versions of bazel
compilation_context.external_includes.to_list() if hasattr(compilation_context, "external_includes") else []
)
),
framework_include_directory = compilation_context.framework_includes.to_list(),
gen_headers = gen_headers.to_list(),
toolchain_id = cc_toolchain_info.id if cc_toolchain_info else None,
)
if not compilation_info and not cc_toolchain_info:
return None
return struct(
compilation_info = compilation_info,
gen_headers = gen_headers,
cc_toolchain_info = cc_toolchain_info,
)
def _collect_dependencies_core_impl(
target,
ctx,
params):
if hasattr(ctx.rule.attr, "tags"):
if "no-ide" in ctx.rule.attr.tags:
return create_dependencies_info(label = target.label)
java_dep_info = _collect_java_dependencies_core_impl(
target,
ctx,
params,
)
cc_dep_info = _collect_cc_dependencies_core_impl(target, ctx)
cc_toolchain_dep_info = _collect_cc_toolchain_info(target, ctx)
return merge_dependencies_info(target, ctx, java_dep_info, cc_dep_info, cc_toolchain_dep_info)
def _collect_java_dependencies_core_impl(
target,
ctx,
params):
target_is_within_project_scope = _target_within_project_scope(target.label, params.include, params.exclude)
dependency_infos = _get_followed_java_dependency_infos(target.label, ctx.rule)
own_and_dependencies = _collect_own_and_dependency_java_artifacts(
target,
ctx,
dependency_infos,
params.always_build_rules,
params.generate_aidl_classes,
params.use_generated_srcjars,
target_is_within_project_scope,
)
if own_and_dependencies == None:
return None
java_info_file = own_and_dependencies.java_info_file
compile_jars = own_and_dependencies.compile_jars
compile_jdeps = own_and_dependencies.compile_jdeps
aars = own_and_dependencies.aars
gensrcs = own_and_dependencies.gensrcs
expand_sources = False
if hasattr(ctx.rule.attr, "tags"):
if "ij-ignore-source-transform" in ctx.rule.attr.tags:
expand_sources = True
return create_java_dependencies_info(
info_file = java_info_file,
info_files = depset([java_info_file], transitive = [d.java_info_files for d in dependency_infos.values()]),
compile_time_jars = compile_jars,
compile_jdeps = compile_jdeps,
aars = aars,
gensrcs = gensrcs,
expand_sources = expand_sources,
)
def _collect_cc_dependencies_core_impl(target, ctx):
cc_info = _collect_own_and_dependency_cc_info(target, ctx.rule)
if not cc_info:
return None
cc_info_files = []
cc_info_files = ([_write_cc_target_info(target.label, cc_info.compilation_info, ctx)] if cc_info.compilation_info else []) + \
([cc_info.cc_toolchain_info.file] if cc_info.cc_toolchain_info else [])
return create_cc_dependencies_info(
cc_info_files = depset(cc_info_files),
cc_compilation_info = cc_info.compilation_info,
cc_headers = cc_info.gen_headers,
cc_toolchain_info = cc_info.cc_toolchain_info,
)
def _collect_cc_toolchain_info(target, ctx):
cc_toolchain_info = IDE_CC.cc_toolchain_info(target, ctx)
if not cc_toolchain_info:
return None
cc_toolchain_file_name = target.label.name + "." + cc_toolchain_info.target_name + ".txt"
cc_toolchain_file = ctx.actions.declare_file(cc_toolchain_file_name)
ctx.actions.write(
cc_toolchain_file,
proto.encode_text(
struct(toolchains = cc_toolchain_info),
),
)
return create_cc_toolchain_info(
cc_toolchain_info = struct(file = cc_toolchain_file, id = cc_toolchain_info.id),
)
def _get_ide_aar_file(target, ctx):
"""
Builds a resource only .aar file for the ide.
The IDE requires just resource files and the manifest from the IDE.
Moreover, there are cases when the existing rules fail to build a full .aar
file from a library, on which other targets can still depend.
The function builds a minimalistic .aar file that contains resources and the
manifest only.
"""
android_info = IDE_ANDROID.get_android_info(target, ctx.rule)
full_aar = android_info.aar
if full_aar:
resource_files = _collect_resource_files(ctx)
resource_map = _build_ide_aar_file_map(android_info.manifest, resource_files)
aar = ctx.actions.declare_file(full_aar.short_path.removesuffix(".aar") + "_ide/" + full_aar.basename)
_package_ide_aar(ctx, aar, resource_map)
return aar
else:
return None
def _collect_resource_files(ctx):
"""
Collects the list of resource files from the target rule attributes.
"""
# Unfortunately, there are no suitable bazel providers that describe
# resource files used a target.
# However, AndroidIdeInfo returns a reference to a so-called resource APK
# file, which contains everything the IDE needs to load resources from a
# given library. However, this format is currently supported by Android
# Studio in the namespaced resource mode. We should consider conditionally
# enabling support in Android Studio and use them in ASwB, instead of
# building special .aar files for the IDE.
resource_files = []
for t in ctx.rule.attr.resource_files:
for f in t.files.to_list():
resource_files.append(f)
return resource_files
def _build_ide_aar_file_map(manifest_file, resource_files):
"""
Build the list of files and their paths as they have to appear in .aar.
"""
file_map = {}
file_map["AndroidManifest.xml"] = manifest_file
for f in resource_files:
res_dir_path = f.short_path \
.removeprefix(android_common.resource_source_directory(f)) \
.removeprefix("/")
if res_dir_path:
res_dir_path = "res/" + res_dir_path
file_map[res_dir_path] = f
return file_map
def _package_ide_aar(ctx, aar, file_map):
"""
Declares a file and defines actions to build .aar according to file_map.
"""
files_map_args = []
files = []
for aar_dir_path, f in file_map.items():
files.append(f)
files_map_args.append("%s=%s" % (aar_dir_path, f.path))
ctx.actions.run(
mnemonic = "GenerateIdeAar",
executable = ctx.executable._build_zip,
inputs = files,
outputs = [aar],
arguments = ["c", aar.path] + files_map_args,
)
# List of tuples containing:
# 1. An attribute for the aspect to traverse
# 2. A list of rule kinds to specify which rules for which the attribute labels
# need to be added as dependencies. If empty, the attribute is followed for
# all rules.
FOLLOW_JAVA_ATTRIBUTES = [
"deps",
"test_deps",
"exports",
"srcs",
"_junit",
"_aspect_proto_toolchain_for_javalite",
"_aspect_java_proto_toolchain",
] + IDE_KOTLIN.follow_attributes
FOLLOW_JAVA_PROTO_ATTRIBUTES = IDE_JAVA_PROTO.follow_attributes
FOLLOW_CC_ATTRIBUTES = IDE_CC.follow_attributes
FOLLOW_ADDITIONAL_ATTRIBUTES = ["runtime", "_toolchain"] + IDE_KOTLIN.follow_additional_attributes
FOLLOW_ATTRIBUTES = _unique(FOLLOW_JAVA_ATTRIBUTES + FOLLOW_JAVA_PROTO_ATTRIBUTES + FOLLOW_CC_ATTRIBUTES + FOLLOW_ADDITIONAL_ATTRIBUTES)
TOOLCHAINS_ASPECTS = IDE_KOTLIN.toolchains_aspects + IDE_JAVA_PROTO.toolchains_aspects + IDE_CC.toolchains_aspects
def collect_dependencies(parameters):
def _impl(target, ctx):
return _collect_dependencies_impl(target, ctx, parameters)
return aspect(
implementation = _impl,
provides = [DependenciesInfo],
attr_aspects = FOLLOW_ATTRIBUTES,
attrs = {
"_build_zip": attr.label(
allow_files = True,
cfg = "exec",
executable = True,
default = ZIP_TOOL_LABEL,
),
},
fragments = ["cpp"],
**{
"toolchains_aspects": TOOLCHAINS_ASPECTS,
} if TOOLCHAINS_ASPECTS else {}
)