xref: /aosp_15_r20/build/bazel/rules/apex/apex_deps_validation.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1# Copyright (C) 2022 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
16load("//build/bazel/rules:common.bzl", "get_dep_targets", "strip_bp2build_label_suffix")
17load("//build/bazel/rules/android:android_app_certificate.bzl", "AndroidAppCertificateInfo")
18load(":apex_available.bzl", "ApexAvailableInfo")
19load(":apex_info.bzl", "ApexInfo")
20load(":apex_key.bzl", "ApexKeyInfo")
21load(":cc.bzl", "get_min_sdk_version")
22
23ApexDepsInfo = provider(
24    "ApexDepsInfo collects transitive deps for dependency validation.",
25    fields = {
26        "transitive_deps": "Labels of targets that are depended on by this APEX.",
27    },
28)
29
30ApexDepInfo = provider(
31    "ApexDepInfo collects metadata about dependencies of APEXs.",
32    fields = {
33        "is_external": "True if this target is an external dep to the APEX.",
34        "label": "Label of target",
35        "min_sdk_version": "min_sdk_version of target",
36    },
37)
38
39_IGNORED_PACKAGES = [
40    "build/bazel/platforms",
41]
42_IGNORED_REPOSITORIES = [
43    "bazel_tools",
44]
45_IGNORED_RULE_KINDS = [
46    # No validation for language-agnostic targets.  In general language
47    # agnostic rules to support AIDL, HIDL, Sysprop do not have an analogous
48    # module type in Soong and do not have an apex_available property, often
49    # relying on language-specific apex_available properties.  Because a
50    # language-specific rule is required for a language-agnostic rule to be
51    # within the transitive deps of an apex and impact the apex contents, this
52    # is safe.
53    "aidl_library",
54    "hidl_library",
55    "sysprop_library",
56
57    # Build settings, these have no built artifact and thus will not be
58    # included in an apex.
59    "string_list_setting",
60    "string_setting",
61
62    # These rule kinds cannot be skipped by checking providers because most
63    # targets have a License provider
64    "_license",
65    "_license_kind",
66]
67_IGNORED_PROVIDERS = [
68    AndroidAppCertificateInfo,
69    ApexKeyInfo,
70    ProtoInfo,
71]
72_IGNORED_ATTRS = [
73    "androidmk_static_deps",
74    "androidmk_whole_archive_deps",
75    "androidmk_dynamic_deps",
76    "androidmk_deps",
77]
78_IGNORED_TARGETS = [
79    "default_metadata_file",
80]
81
82def _should_skip_apex_dep(target, ctx):
83    # Ignore Bazel-specific targets like platform/os/arch constraints,
84    # anything from @bazel_tools, and rule types that we dont care about
85    # for dependency validation like licenses, certificates, etc.
86    #TODO(b/261715581) update allowed_deps.txt to include Bazel-specific targets
87    return (
88        ctx.label.workspace_name in _IGNORED_REPOSITORIES or
89        ctx.label.package in _IGNORED_PACKAGES or
90        ctx.rule.kind in _IGNORED_RULE_KINDS or
91        True in [p in target for p in _IGNORED_PROVIDERS] or
92        target.label.name in _IGNORED_TARGETS
93    )
94
95def _apex_dep_validation_aspect_impl(target, ctx):
96    transitive_deps = []
97    for attr, attr_deps in get_dep_targets(ctx.rule.attr, predicate = lambda target: ApexDepsInfo in target).items():
98        if attr in _IGNORED_ATTRS:
99            continue
100        for dep in attr_deps:
101            transitive_deps.append(dep[ApexDepsInfo].transitive_deps)
102
103    if _should_skip_apex_dep(target, ctx):
104        return ApexDepsInfo(
105            transitive_deps = depset(
106                transitive = transitive_deps,
107            ),
108        )
109
110    is_external = False
111    include_self_in_transitive_deps = True
112
113    if "manual" in ctx.rule.attr.tags and "apex_available_checked_manual_for_testing" not in ctx.rule.attr.tags:
114        include_self_in_transitive_deps = False
115    else:
116        apex_available_names = target[ApexAvailableInfo].apex_available_names
117        apex_name = ctx.attr._apex_name[BuildSettingInfo].value
118        base_apex_name = ctx.attr._base_apex_name[BuildSettingInfo].value
119        if not (
120            "//apex_available:anyapex" in apex_available_names or
121            base_apex_name in apex_available_names or
122            apex_name in apex_available_names
123        ):
124            # APEX deps validation stops when the dependency graph crosses the APEX boundary
125            # Record that this is a boundary target, so that we exclude can it later from validation
126            is_external = True
127            transitive_deps = []
128
129        if not target[ApexAvailableInfo].platform_available:
130            # Skip dependencies that are only available to APEXes; they are
131            # developed with updatability in mind and don't need manual approval.
132            include_self_in_transitive_deps = False
133
134    if ApexInfo in target:
135        include_self_in_transitive_deps = False
136
137    direct_deps = []
138    if include_self_in_transitive_deps:
139        direct_deps = [
140            ApexDepInfo(
141                label = ctx.label,
142                is_external = is_external,
143                min_sdk_version = get_min_sdk_version(ctx),
144            ),
145        ]
146
147    return ApexDepsInfo(
148        transitive_deps = depset(
149            direct = direct_deps,
150            transitive = transitive_deps,
151        ),
152    )
153
154apex_deps_validation_aspect = aspect(
155    doc = "apex_deps_validation_aspect walks the deps of an APEX and records" +
156          " its transitive dependencies so that they can be validated against" +
157          " allowed_deps.txt.",
158    implementation = _apex_dep_validation_aspect_impl,
159    attr_aspects = ["*"],
160    apply_to_generating_rules = True,
161    attrs = {
162        "_apex_name": attr.label(default = "//build/bazel/rules/apex:apex_name"),
163        "_base_apex_name": attr.label(default = "//build/bazel/rules/apex:base_apex_name"),
164        "_direct_deps": attr.label(default = "//build/bazel/rules/apex:apex_direct_deps"),
165    },
166    required_aspect_providers = [ApexAvailableInfo],
167    provides = [ApexDepsInfo],
168)
169
170def _min_sdk_version_string(version):
171    if version.apex_inherit:
172        return "apex_inherit"
173    elif version.min_sdk_version == None:
174        return "(no version)"
175    return version.min_sdk_version
176
177def _apex_dep_to_string(apex_dep_info):
178    return "{name}(minSdkVersion:{min_sdk_version})".format(
179        name = strip_bp2build_label_suffix(apex_dep_info.label.name),
180        min_sdk_version = _min_sdk_version_string(apex_dep_info.min_sdk_version),
181    )
182
183def apex_dep_infos_to_allowlist_strings(apex_dep_infos):
184    """apex_dep_infos_to_allowlist_strings converts outputs a string that can be compared against allowed_deps.txt
185
186    Args:
187        apex_dep_infos (list[ApexDepInfo]): list of deps to convert
188    Returns:
189        a list of strings conforming to the format of allowed_deps.txt
190    """
191    return [
192        _apex_dep_to_string(d)
193        for d in apex_dep_infos
194        if not d.is_external
195    ]
196
197def validate_apex_deps(ctx, transitive_deps, allowed_deps_manifest):
198    """validate_apex_deps generates actions to validate that all deps in transitive_deps exist in the allowed_deps file
199
200    Args:
201        ctx (rule context): a rule context
202        transitive_deps (depset[ApexDepsInfo]): list of transitive dependencies
203            of an APEX. This is most likely generated by collecting the output
204            of apex_deps_validation_aspect
205        allowed_deps_manifest (File): a file containing an allowlist of modules
206            that can be included in an APEX. This is expected to be in the format
207            of //packages/modules/common/build/allowed_deps.txt
208    Returns:
209        validation_marker (File): an empty file created if validation succeeds
210    """
211    apex_deps_file = ctx.actions.declare_file(ctx.label.name + ".current_deps")
212    ctx.actions.write(
213        apex_deps_file,
214        "\n".join(apex_dep_infos_to_allowlist_strings(transitive_deps.to_list())),
215    )
216    validation_marker = ctx.actions.declare_file(ctx.label.name + ".allowed_deps")
217    shell_command = """
218        export module_diff=$(
219            cat {allowed_deps_manifest} |
220            sed -e 's/^prebuilt_//g' |
221            sort |
222            comm -23 <(sort -u {apex_deps_file}) -
223        );
224        export diff_size=$(echo "$module_diff" | wc -w);
225        if [[ $diff_size -eq 0 ]]; then
226            touch {validation_marker};
227        else
228            echo -e "\n******************************";
229            echo "ERROR: go/apex-allowed-deps-error contains more information";
230            echo "******************************";
231            echo "Detected changes to allowed dependencies in updatable modules.";
232            echo "There are $diff_size dependencies of APEX {target_label} on modules not in {allowed_deps_manifest}:";
233            echo "$module_diff";
234            echo "To fix and update packages/modules/common/build/allowed_deps.txt, please run:";
235            echo -e "$ (croot && packages/modules/common/build/update-apex-allowed-deps.sh)\n";
236            echo "When submitting the generated CL, you must include the following information";
237            echo "in the commit message if you are adding a new dependency:";
238            echo "Apex-Size-Increase: Expected binary size increase for affected APEXes (or the size of the .jar / .so file of the new library)";
239            echo "Previous-Platform-Support: Are the maintainers of the new dependency committed to supporting previous platform releases?";
240            echo "Aosp-First: Is the new dependency being developed AOSP-first or internal?";
241            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?";
242            echo "You do not need OWNERS approval to submit the change, but mainline-modularization@";
243            echo "will periodically review additions and may require changes.";
244            echo -e "******************************\n";
245            exit 1;
246        fi;
247    """.format(
248        allowed_deps_manifest = allowed_deps_manifest.path,
249        apex_deps_file = apex_deps_file.path,
250        validation_marker = validation_marker.path,
251        target_label = ctx.label,
252    )
253    ctx.actions.run_shell(
254        inputs = [allowed_deps_manifest, apex_deps_file],
255        outputs = [validation_marker],
256        command = shell_command,
257        mnemonic = "ApexDepValidation",
258        progress_message = "Validating APEX dependencies",
259    )
260
261    return validation_marker
262