262 lines
10 KiB
Python
262 lines
10 KiB
Python
# 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: What’s 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
|