blob: 066ad1a99ac4d8d45e31da72f3e2fd1fde6aa076 [file] [log] [blame] [edit]
# Copyright 2023 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.
"""Aspect that transitively build .dex archives and desugar jars."""
load(":utils.bzl", _get_android_sdk = "get_android_sdk", _utils = "utils")
load(":dex.bzl", _dex = "dex")
load(":desugar.bzl", _desugar = "desugar")
load(":providers.bzl", "StarlarkAndroidDexInfo")
load(":attrs.bzl", _attrs = "attrs")
load("//rules:acls.bzl", "acls")
_tristate = _attrs.tristate
def _aspect_attrs():
"""Attrs of the rule requiring traversal by the aspect."""
return [
"aidl_lib", # for the aidl runtime in the android_sdk rule
"deps",
"exports",
"runtime",
"runtime_deps",
"_android_sdk",
"_aspect_proto_toolchain_for_javalite", # To get from proto_library through proto_lang_toolchain rule to proto runtime library.
"_build_stamp_deps", # for build stamp runtime class deps
"_build_stamp_mergee_manifest_lib", # for empty build stamp Service class implementation
"_toolchain", # to get Kotlin toolchain component in android_library
]
# Also used by the android_binary_internal rule
def get_aspect_deps(ctx):
"""Get all the deps of the dex_desugar_aspect that requires traversal.
Args:
ctx: The context.
Returns:
deps_list: List of all deps of the dex_desugar_aspect that requires traversal.
"""
deps_list = []
for deps in [getattr(ctx.attr, attr, []) for attr in _aspect_attrs()]:
if str(type(deps)) == "list":
deps_list += deps
elif str(type(deps)) == "Target":
deps_list.append(deps)
return deps_list
def _aspect_impl(target, ctx):
"""Adapts the rule and target data.
Args:
target: The target.
ctx: The context.
Returns:
A list of providers.
"""
if not acls.in_android_binary_starlark_dex_desugar_proguard(str(ctx.label)):
return []
incremental_dexing = getattr(ctx.rule.attr, "incremental_dexing", _tristate.auto)
min_sdk_version = getattr(ctx.rule.attr, "min_sdk_version", 0)
if incremental_dexing == _tristate.no or \
(not ctx.fragments.android.use_incremental_dexing and
incremental_dexing == _tristate.auto):
return []
# TODO(b/33557068): Desugar protos if needed instead of assuming they don't need desugaring
ignore_desugar = not ctx.fragments.android.desugar_java8 or ctx.rule.kind == "proto_library"
extra_toolchain_jars = _get_platform_based_toolchain_jars(ctx)
if hasattr(ctx.rule.attr, "neverlink") and ctx.rule.attr.neverlink:
return []
dex_archives_dict = {}
runtime_jars = _get_produced_runtime_jars(target, ctx, extra_toolchain_jars)
bootclasspath = _get_boot_classpath(target, ctx)
compiletime_classpath = target[JavaInfo].transitive_compile_time_jars if JavaInfo in target else None
if runtime_jars:
basename_clash = _check_basename_clash(runtime_jars)
aspect_dexopts = _get_aspect_dexopts(ctx)
min_sdk_filename_part = "--min_sdk_version=" + min_sdk_version if min_sdk_version > 0 else ""
for jar in runtime_jars:
if not ignore_desugar:
unique_desugar_filename = (jar.path if basename_clash else jar.basename) + \
min_sdk_filename_part + "_desugared.jar"
desugared_jar = _dex.get_dx_artifact(ctx, unique_desugar_filename)
_desugar.desugar(
ctx,
input = jar,
output = desugared_jar,
bootclasspath = bootclasspath,
classpath = compiletime_classpath,
min_sdk_version = min_sdk_version,
desugar_exec = ctx.executable._desugar_java8,
)
else:
desugared_jar = None
for incremental_dexopts_list in aspect_dexopts:
incremental_dexopts = "".join(incremental_dexopts_list)
unique_dx_filename = (jar.short_path if basename_clash else jar.basename) + \
incremental_dexopts + min_sdk_filename_part + ".dex.zip"
dex = _dex.get_dx_artifact(ctx, unique_dx_filename)
_dex.dex(
ctx,
input = desugared_jar if desugared_jar else jar,
output = dex,
incremental_dexopts = incremental_dexopts_list,
min_sdk_version = min_sdk_version,
dex_exec = ctx.executable._dexbuilder,
)
dex_archive = struct(
jar = jar,
desugared_jar = desugared_jar,
dex = dex,
)
if incremental_dexopts not in dex_archives_dict:
dex_archives_dict[incremental_dexopts] = []
dex_archives_dict[incremental_dexopts].append(dex_archive)
infos = _utils.collect_providers(StarlarkAndroidDexInfo, get_aspect_deps(ctx.rule))
merged_info = _dex.merge_infos(infos)
for dexopts in dex_archives_dict:
if dexopts in merged_info.dex_archives_dict:
merged_info.dex_archives_dict[dexopts] = depset(dex_archives_dict[dexopts], transitive = [merged_info.dex_archives_dict[dexopts]])
else:
merged_info.dex_archives_dict[dexopts] = depset(dex_archives_dict[dexopts])
return [
StarlarkAndroidDexInfo(
dex_archives_dict = merged_info.dex_archives_dict,
),
]
def _get_produced_runtime_jars(target, ctx, extra_toolchain_jars):
if ctx.rule.kind == "proto_library":
if getattr(ctx.rule.attr, "srcs", []):
if JavaInfo in target:
return [java_output.class_jar for java_output in target[JavaInfo].java_outputs]
return []
else:
jars = []
if JavaInfo in target:
jars.extend(target[JavaInfo].runtime_output_jars)
# TODO(b/124540821): Disable R.jar desugaring (with a flag).
if AndroidIdeInfo in target and target[AndroidIdeInfo].resource_jar:
jars.append(target[AndroidIdeInfo].resource_jar.class_jar)
if AndroidApplicationResourceInfo in target and target[AndroidApplicationResourceInfo].build_stamp_jar:
jars.append(target[AndroidApplicationResourceInfo].build_stamp_jar)
jars.extend(extra_toolchain_jars)
return jars
def _get_platform_based_toolchain_jars(ctx):
if not ctx.fragments.android.incompatible_use_toolchain_resolution:
return []
if not getattr(ctx.rule.attr, "_android_sdk", None):
return []
android_sdk = ctx.rule.attr._android_sdk
if AndroidSdkInfo in android_sdk and android_sdk[AndroidSdkInfo].aidl_lib:
return android_sdk[AndroidSdkInfo].aidl_lib[JavaInfo].runtime_output_jars
return []
def _get_aspect_dexopts(ctx):
return _power_set(_dex.normalize_dexopts(ctx.fragments.android.get_dexopts_supported_in_incremental_dexing))
def _get_boot_classpath(target, ctx):
if JavaInfo in target:
compilation_info = target[JavaInfo].compilation_info
if compilation_info and compilation_info.boot_classpath:
return compilation_info.boot_classpath
android_jar = _get_android_sdk(ctx).android_jar
if android_jar:
return [android_jar]
# This shouldn't ever be reached, but if it is, we should be clear about the error.
fail("No compilation info or android jar!")
def _check_basename_clash(artifacts):
seen = {}
for artifact in artifacts:
basename = artifact.basename
if basename not in seen:
seen[basename] = True
else:
return True
return False
def _power_set(items):
"""Calculates the power set of the given items.
"""
def _exp(base, n):
""" Calculates base ** n."""
res = 1
for _ in range(n):
res *= base
return res
power_set = []
size = len(items)
for i in range(_exp(2, size)):
element = [items[j] for j in range(size) if (i // _exp(2, j) % 2) != 0]
power_set.append(element)
return power_set
dex_desugar_aspect = aspect(
implementation = _aspect_impl,
attr_aspects = _aspect_attrs(),
attrs = _attrs.add(
{
"_desugar_java8": attr.label(
default = Label("@bazel_tools//tools/android:desugar_java8"),
cfg = "exec",
executable = True,
),
"_dexbuilder": attr.label(
default = Label("@bazel_tools//tools/android:dexbuilder"),
allow_files = True,
cfg = "exec",
executable = True,
),
},
_attrs.ANDROID_SDK,
),
fragments = ["android"],
toolchains = ["//toolchains/android_sdk:toolchain_type"],
required_aspect_providers = [[JavaInfo]],
)