unplugged-system/build/bazel/rules/apex/apex_deps_validation.bzl

262 lines
10 KiB
Python
Raw Permalink Normal View History

# Copyright (C) 2022 The Android Open Source Project
#
# 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.
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("//build/bazel/rules:common.bzl", "get_dep_targets", "strip_bp2build_label_suffix")
load("//build/bazel/rules/android:android_app_certificate.bzl", "AndroidAppCertificateInfo")
load(":apex_available.bzl", "ApexAvailableInfo")
load(":apex_info.bzl", "ApexInfo")
load(":apex_key.bzl", "ApexKeyInfo")
load(":cc.bzl", "get_min_sdk_version")
ApexDepsInfo = provider(
"ApexDepsInfo collects transitive deps for dependency validation.",
fields = {
"transitive_deps": "Labels of targets that are depended on by this APEX.",
},
)
ApexDepInfo = provider(
"ApexDepInfo collects metadata about dependencies of APEXs.",
fields = {
"is_external": "True if this target is an external dep to the APEX.",
"label": "Label of target",
"min_sdk_version": "min_sdk_version of target",
},
)
_IGNORED_PACKAGES = [
"build/bazel/platforms",
]
_IGNORED_REPOSITORIES = [
"bazel_tools",
]
_IGNORED_RULE_KINDS = [
# No validation for language-agnostic targets. In general language
# agnostic rules to support AIDL, HIDL, Sysprop do not have an analogous
# module type in Soong and do not have an apex_available property, often
# relying on language-specific apex_available properties. Because a
# language-specific rule is required for a language-agnostic rule to be
# within the transitive deps of an apex and impact the apex contents, this
# is safe.
"aidl_library",
"hidl_library",
"sysprop_library",
# Build settings, these have no built artifact and thus will not be
# included in an apex.
"string_list_setting",
"string_setting",
# These rule kinds cannot be skipped by checking providers because most
# targets have a License provider
"_license",
"_license_kind",
]
_IGNORED_PROVIDERS = [
AndroidAppCertificateInfo,
ApexKeyInfo,
ProtoInfo,
]
_IGNORED_ATTRS = [
"androidmk_static_deps",
"androidmk_whole_archive_deps",
"androidmk_dynamic_deps",
"androidmk_deps",
]
_IGNORED_TARGETS = [
"default_metadata_file",
]
def _should_skip_apex_dep(target, ctx):
# Ignore Bazel-specific targets like platform/os/arch constraints,
# anything from @bazel_tools, and rule types that we dont care about
# for dependency validation like licenses, certificates, etc.
#TODO(b/261715581) update allowed_deps.txt to include Bazel-specific targets
return (
ctx.label.workspace_name in _IGNORED_REPOSITORIES or
ctx.label.package in _IGNORED_PACKAGES or
ctx.rule.kind in _IGNORED_RULE_KINDS or
True in [p in target for p in _IGNORED_PROVIDERS] or
target.label.name in _IGNORED_TARGETS
)
def _apex_dep_validation_aspect_impl(target, ctx):
transitive_deps = []
for attr, attr_deps in get_dep_targets(ctx.rule.attr, predicate = lambda target: ApexDepsInfo in target).items():
if attr in _IGNORED_ATTRS:
continue
for dep in attr_deps:
transitive_deps.append(dep[ApexDepsInfo].transitive_deps)
if _should_skip_apex_dep(target, ctx):
return ApexDepsInfo(
transitive_deps = depset(
transitive = transitive_deps,
),
)
is_external = False
include_self_in_transitive_deps = True
if "manual" in ctx.rule.attr.tags and "apex_available_checked_manual_for_testing" not in ctx.rule.attr.tags:
include_self_in_transitive_deps = False
else:
apex_available_names = target[ApexAvailableInfo].apex_available_names
apex_name = ctx.attr._apex_name[BuildSettingInfo].value
base_apex_name = ctx.attr._base_apex_name[BuildSettingInfo].value
if not (
"//apex_available:anyapex" in apex_available_names or
base_apex_name in apex_available_names or
apex_name in apex_available_names
):
# APEX deps validation stops when the dependency graph crosses the APEX boundary
# Record that this is a boundary target, so that we exclude can it later from validation
is_external = True
transitive_deps = []
if not target[ApexAvailableInfo].platform_available:
# Skip dependencies that are only available to APEXes; they are
# developed with updatability in mind and don't need manual approval.
include_self_in_transitive_deps = False
if ApexInfo in target:
include_self_in_transitive_deps = False
direct_deps = []
if include_self_in_transitive_deps:
direct_deps = [
ApexDepInfo(
label = ctx.label,
is_external = is_external,
min_sdk_version = get_min_sdk_version(ctx),
),
]
return ApexDepsInfo(
transitive_deps = depset(
direct = direct_deps,
transitive = transitive_deps,
),
)
apex_deps_validation_aspect = aspect(
doc = "apex_deps_validation_aspect walks the deps of an APEX and records" +
" its transitive dependencies so that they can be validated against" +
" allowed_deps.txt.",
implementation = _apex_dep_validation_aspect_impl,
attr_aspects = ["*"],
apply_to_generating_rules = True,
attrs = {
"_apex_name": attr.label(default = "//build/bazel/rules/apex:apex_name"),
"_base_apex_name": attr.label(default = "//build/bazel/rules/apex:base_apex_name"),
"_direct_deps": attr.label(default = "//build/bazel/rules/apex:apex_direct_deps"),
},
required_aspect_providers = [ApexAvailableInfo],
provides = [ApexDepsInfo],
)
def _min_sdk_version_string(version):
if version.apex_inherit:
return "apex_inherit"
elif version.min_sdk_version == None:
return "(no version)"
return version.min_sdk_version
def _apex_dep_to_string(apex_dep_info):
return "{name}(minSdkVersion:{min_sdk_version})".format(
name = strip_bp2build_label_suffix(apex_dep_info.label.name),
min_sdk_version = _min_sdk_version_string(apex_dep_info.min_sdk_version),
)
def apex_dep_infos_to_allowlist_strings(apex_dep_infos):
"""apex_dep_infos_to_allowlist_strings converts outputs a string that can be compared against allowed_deps.txt
Args:
apex_dep_infos (list[ApexDepInfo]): list of deps to convert
Returns:
a list of strings conforming to the format of allowed_deps.txt
"""
return [
_apex_dep_to_string(d)
for d in apex_dep_infos
if not d.is_external
]
def validate_apex_deps(ctx, transitive_deps, allowed_deps_manifest):
"""validate_apex_deps generates actions to validate that all deps in transitive_deps exist in the allowed_deps file
Args:
ctx (rule context): a rule context
transitive_deps (depset[ApexDepsInfo]): list of transitive dependencies
of an APEX. This is most likely generated by collecting the output
of apex_deps_validation_aspect
allowed_deps_manifest (File): a file containing an allowlist of modules
that can be included in an APEX. This is expected to be in the format
of //packages/modules/common/build/allowed_deps.txt
Returns:
validation_marker (File): an empty file created if validation succeeds
"""
apex_deps_file = ctx.actions.declare_file(ctx.label.name + ".current_deps")
ctx.actions.write(
apex_deps_file,
"\n".join(apex_dep_infos_to_allowlist_strings(transitive_deps.to_list())),
)
validation_marker = ctx.actions.declare_file(ctx.label.name + ".allowed_deps")
shell_command = """
export module_diff=$(
cat {allowed_deps_manifest} |
sed -e 's/^prebuilt_//g' |
sort |
comm -23 <(sort -u {apex_deps_file}) -
);
export diff_size=$(echo "$module_diff" | wc -w);
if [[ $diff_size -eq 0 ]]; then
touch {validation_marker};
else
echo -e "\n******************************";
echo "ERROR: go/apex-allowed-deps-error contains more information";
echo "******************************";
echo "Detected changes to allowed dependencies in updatable modules.";
echo "There are $diff_size dependencies of APEX {target_label} on modules not in {allowed_deps_manifest}:";
echo "$module_diff";
echo "To fix and update packages/modules/common/build/allowed_deps.txt, please run:";
echo -e "$ (croot && packages/modules/common/build/update-apex-allowed-deps.sh)\n";
echo "When submitting the generated CL, you must include the following information";
echo "in the commit message if you are adding a new dependency:";
echo "Apex-Size-Increase: Expected binary size increase for affected APEXes (or the size of the .jar / .so file of the new library)";
echo "Previous-Platform-Support: Are the maintainers of the new dependency committed to supporting previous platform releases?";
echo "Aosp-First: Is the new dependency being developed AOSP-first or internal?";
echo "Test-Info: Whats the testing strategy for the new dependency? Does it have its own tests, and are you adding integration tests? How/when are the tests run?";
echo "You do not need OWNERS approval to submit the change, but mainline-modularization@";
echo "will periodically review additions and may require changes.";
echo -e "******************************\n";
exit 1;
fi;
""".format(
allowed_deps_manifest = allowed_deps_manifest.path,
apex_deps_file = apex_deps_file.path,
validation_marker = validation_marker.path,
target_label = ctx.label,
)
ctx.actions.run_shell(
inputs = [allowed_deps_manifest, apex_deps_file],
outputs = [validation_marker],
command = shell_command,
mnemonic = "ApexDepValidation",
progress_message = "Validating APEX dependencies",
)
return validation_marker