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