1# Copyright 2021 The Bazel Authors. All rights reserved. 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 15"""android_application rule.""" 16 17load(":android_feature_module_rule.bzl", "get_feature_module_paths") 18load(":attrs.bzl", "ANDROID_APPLICATION_ATTRS") 19load( 20 "//rules:aapt.bzl", 21 _aapt = "aapt", 22) 23load( 24 "//rules:bundletool.bzl", 25 _bundletool = "bundletool", 26) 27load( 28 "//rules:busybox.bzl", 29 _busybox = "busybox", 30) 31load( 32 "//rules:common.bzl", 33 _common = "common", 34) 35load( 36 "//rules:java.bzl", 37 _java = "java", 38) 39load( 40 "//rules:providers.bzl", 41 "AndroidBundleInfo", 42 "AndroidFeatureModuleInfo", 43 "StarlarkAndroidResourcesInfo", 44) 45load( 46 "//rules:utils.bzl", 47 "get_android_toolchain", 48 _log = "log", 49) 50 51UNSUPPORTED_ATTRS = [ 52 "srcs", 53] 54 55def _verify_attrs(attrs, fqn): 56 for attr in UNSUPPORTED_ATTRS: 57 if hasattr(attrs, attr): 58 _log.error("Unsupported attr: %s in android_application" % attr) 59 60 if not attrs.get("manifest_values", {}).get("applicationId"): 61 _log.error("%s missing required applicationId in manifest_values" % fqn) 62 63 for attr in ["deps"]: 64 if attr not in attrs: 65 _log.error("%s missing require attribute `%s`" % (fqn, attr)) 66 67def _process_feature_module( 68 ctx, 69 out = None, 70 base_apk = None, 71 feature_target = None, 72 java_package = None, 73 application_id = None): 74 manifest = _create_feature_manifest( 75 ctx, 76 base_apk, 77 java_package, 78 feature_target, 79 ctx.attr._android_sdk[AndroidSdkInfo].aapt2, 80 ctx.executable._feature_manifest_script, 81 ctx.executable._priority_feature_manifest_script, 82 get_android_toolchain(ctx).android_resources_busybox, 83 _common.get_host_javabase(ctx), 84 ) 85 res = feature_target[AndroidFeatureModuleInfo].library[StarlarkAndroidResourcesInfo] 86 binary = feature_target[AndroidFeatureModuleInfo].binary[ApkInfo].unsigned_apk 87 has_native_libs = bool(feature_target[AndroidFeatureModuleInfo].binary[AndroidIdeInfo].native_libs) 88 89 # Create res .proto-apk_, output depending on whether this split has native libs. 90 if has_native_libs: 91 res_apk = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "/res.proto-ap_") 92 else: 93 res_apk = out 94 _busybox.package( 95 ctx, 96 out_r_src_jar = ctx.actions.declare_file("R.srcjar", sibling = manifest), 97 out_r_txt = ctx.actions.declare_file("R.txt", sibling = manifest), 98 out_symbols = ctx.actions.declare_file("merged.bin", sibling = manifest), 99 out_manifest = ctx.actions.declare_file("AndroidManifest_processed.xml", sibling = manifest), 100 out_proguard_cfg = ctx.actions.declare_file("proguard.cfg", sibling = manifest), 101 out_main_dex_proguard_cfg = ctx.actions.declare_file( 102 "main_dex_proguard.cfg", 103 sibling = manifest, 104 ), 105 out_resource_files_zip = ctx.actions.declare_file("resource_files.zip", sibling = manifest), 106 out_file = res_apk, 107 manifest = manifest, 108 java_package = java_package, 109 direct_resources_nodes = res.direct_resources_nodes, 110 transitive_resources_nodes = res.transitive_resources_nodes, 111 transitive_manifests = [res.transitive_manifests], 112 transitive_assets = [res.transitive_assets], 113 transitive_compiled_assets = [res.transitive_compiled_assets], 114 transitive_resource_files = [res.transitive_resource_files], 115 transitive_compiled_resources = [res.transitive_compiled_resources], 116 transitive_r_txts = [res.transitive_r_txts], 117 additional_apks_to_link_against = [base_apk], 118 proto_format = True, # required for aab. 119 android_jar = ctx.attr._android_sdk[AndroidSdkInfo].android_jar, 120 aapt = get_android_toolchain(ctx).aapt2.files_to_run, 121 busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run, 122 host_javabase = _common.get_host_javabase(ctx), 123 should_throw_on_conflict = True, 124 application_id = application_id, 125 ) 126 127 if not has_native_libs: 128 return 129 130 # Extract libs/ from split binary 131 native_libs = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "/native_libs.zip") 132 _common.filter_zip_include(ctx, binary, native_libs, ["lib/*"]) 133 134 # Extract AndroidManifest.xml and assets from res-ap_ 135 filtered_res = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "/filtered_res.zip") 136 _common.filter_zip_include(ctx, res_apk, filtered_res, ["AndroidManifest.xml", "assets/*"]) 137 138 # Merge into output 139 _java.singlejar( 140 ctx, 141 inputs = [filtered_res, native_libs], 142 output = out, 143 java_toolchain = _common.get_java_toolchain(ctx), 144 ) 145 146def _create_feature_manifest( 147 ctx, 148 base_apk, 149 java_package, 150 feature_target, 151 aapt2, 152 feature_manifest_script, 153 priority_feature_manifest_script, 154 android_resources_busybox, 155 host_javabase): 156 info = feature_target[AndroidFeatureModuleInfo] 157 manifest = ctx.actions.declare_file(ctx.label.name + "/" + feature_target.label.name + "/AndroidManifest.xml") 158 159 # Rule has not specified a manifest. Populate the default manifest template. 160 if not info.manifest: 161 args = ctx.actions.args() 162 args.add(manifest.path) 163 args.add(base_apk.path) 164 args.add(java_package) 165 args.add(info.feature_name) 166 args.add(info.title_id) 167 args.add(info.fused) 168 args.add(aapt2.executable) 169 170 ctx.actions.run( 171 executable = feature_manifest_script, 172 inputs = [base_apk], 173 outputs = [manifest], 174 arguments = [args], 175 tools = [ 176 aapt2, 177 ], 178 mnemonic = "GenFeatureManifest", 179 progress_message = "Generating AndroidManifest.xml for " + feature_target.label.name, 180 toolchain = None, 181 ) 182 return manifest 183 184 # Rule has a manifest (already validated by android_feature_module). 185 # Generate a priority manifest and then merge the user supplied manifest. 186 priority_manifest = ctx.actions.declare_file( 187 ctx.label.name + "/" + feature_target.label.name + "/Prioriy_AndroidManifest.xml", 188 ) 189 args = ctx.actions.args() 190 args.add(priority_manifest.path) 191 args.add(base_apk.path) 192 args.add(java_package) 193 args.add(info.feature_name) 194 args.add(aapt2.executable) 195 ctx.actions.run( 196 executable = priority_feature_manifest_script, 197 inputs = [base_apk], 198 outputs = [priority_manifest], 199 arguments = [args], 200 tools = [ 201 aapt2, 202 ], 203 mnemonic = "GenPriorityFeatureManifest", 204 progress_message = "Generating Priority AndroidManifest.xml for " + feature_target.label.name, 205 toolchain = None, 206 ) 207 208 args = ctx.actions.args() 209 args.add("--main_manifest", priority_manifest.path) 210 args.add("--feature_manifest", info.manifest.path) 211 args.add("--feature_title", "@string/" + info.title_id) 212 args.add("--out", manifest.path) 213 ctx.actions.run( 214 executable = ctx.attr._merge_manifests.files_to_run, 215 inputs = [priority_manifest, info.manifest], 216 outputs = [manifest], 217 arguments = [args], 218 toolchain = None, 219 ) 220 221 return manifest 222 223def _impl(ctx): 224 # Convert base apk to .proto_ap_ 225 base_apk = ctx.attr.base_module[ApkInfo].unsigned_apk 226 base_proto_apk = ctx.actions.declare_file(ctx.label.name + "/modules/base.proto-ap_") 227 _aapt.convert( 228 ctx, 229 out = base_proto_apk, 230 input = base_apk, 231 to_proto = True, 232 aapt = get_android_toolchain(ctx).aapt2.files_to_run, 233 ) 234 proto_apks = [base_proto_apk] 235 236 # Convert each feature to .proto-ap_ 237 for feature in ctx.attr.feature_modules: 238 feature_proto_apk = ctx.actions.declare_file( 239 "%s.proto-ap_" % feature.label.name, 240 sibling = base_proto_apk, 241 ) 242 _process_feature_module( 243 ctx, 244 out = feature_proto_apk, 245 base_apk = base_apk, 246 feature_target = feature, 247 java_package = _java.resolve_package_from_label(ctx.label, ctx.attr.custom_package), 248 application_id = ctx.attr.application_id, 249 ) 250 proto_apks.append(feature_proto_apk) 251 252 # Convert each each .proto-ap_ to module zip 253 modules = [] 254 for proto_apk in proto_apks: 255 module = ctx.actions.declare_file( 256 proto_apk.basename + ".zip", 257 sibling = proto_apk, 258 ) 259 modules.append(module) 260 _bundletool.proto_apk_to_module( 261 ctx, 262 out = module, 263 proto_apk = proto_apk, 264 bundletool_module_builder = 265 get_android_toolchain(ctx).bundletool_module_builder.files_to_run, 266 ) 267 268 metadata = dict() 269 if ProguardMappingInfo in ctx.attr.base_module: 270 metadata["com.android.tools.build.obfuscation/proguard.map"] = ctx.attr.base_module[ProguardMappingInfo].proguard_mapping 271 272 if ctx.file.rotation_config: 273 metadata["com.google.play.apps.signing/RotationConfig.textproto"] = ctx.file.rotation_config 274 275 if ctx.file.app_integrity_config: 276 metadata["com.google.play.apps.integrity/AppIntegrityConfig.pb"] = ctx.file.app_integrity_config 277 278 # Create .aab 279 _bundletool.build( 280 ctx, 281 out = ctx.outputs.unsigned_aab, 282 modules = modules, 283 config = ctx.file.bundle_config_file, 284 metadata = metadata, 285 bundletool = get_android_toolchain(ctx).bundletool.files_to_run, 286 host_javabase = _common.get_host_javabase(ctx), 287 ) 288 289 # Create `blaze run` script 290 base_apk_info = ctx.attr.base_module[ApkInfo] 291 deploy_script_files = [base_apk_info.signing_keys[-1]] 292 subs = { 293 "%bundletool_path%": get_android_toolchain(ctx).bundletool.files_to_run.executable.short_path, 294 "%aab%": ctx.outputs.unsigned_aab.short_path, 295 "%newest_key%": base_apk_info.signing_keys[-1].short_path, 296 } 297 if base_apk_info.signing_lineage: 298 signer_properties = _common.create_signer_properties(ctx, base_apk_info.signing_keys[0]) 299 subs["%oldest_signer_properties%"] = signer_properties.short_path 300 subs["%lineage%"] = base_apk_info.signing_lineage.short_path 301 subs["%min_rotation_api%"] = base_apk_info.signing_min_v3_rotation_api_version 302 deploy_script_files.extend( 303 [signer_properties, base_apk_info.signing_lineage, base_apk_info.signing_keys[0]], 304 ) 305 else: 306 subs["%oldest_signer_properties%"] = "" 307 subs["%lineage%"] = "" 308 subs["%min_rotation_api%"] = "" 309 ctx.actions.expand_template( 310 template = ctx.file._bundle_deploy, 311 output = ctx.outputs.deploy_script, 312 substitutions = subs, 313 is_executable = True, 314 ) 315 316 return [ 317 ctx.attr.base_module[ApkInfo], 318 ctx.attr.base_module[AndroidPreDexJarInfo], 319 AndroidBundleInfo(unsigned_aab = ctx.outputs.unsigned_aab), 320 DefaultInfo( 321 executable = ctx.outputs.deploy_script, 322 runfiles = ctx.runfiles([ 323 ctx.outputs.unsigned_aab, 324 get_android_toolchain(ctx).bundletool.files_to_run.executable, 325 ] + deploy_script_files), 326 ), 327 ] 328 329android_application = rule( 330 attrs = ANDROID_APPLICATION_ATTRS, 331 cfg = android_common.android_platforms_transition, 332 fragments = [ 333 "android", 334 "java", 335 ], 336 executable = True, 337 implementation = _impl, 338 outputs = { 339 "deploy_script": "%{name}.sh", 340 "unsigned_aab": "%{name}_unsigned.aab", 341 }, 342 toolchains = [ 343 "//toolchains/android:toolchain_type", 344 "@bazel_tools//tools/jdk:toolchain_type", 345 ], 346 _skylark_testable = True, 347) 348 349def android_application_macro(_android_binary, **attrs): 350 """android_application_macro. 351 352 Args: 353 _android_binary: The android_binary rule to use. 354 **attrs: android_application attributes. 355 """ 356 357 fqn = "//%s:%s" % (native.package_name(), attrs["name"]) 358 359 # Must pop these because android_binary does not have these attributes. 360 app_integrity_config = attrs.pop("app_integrity_config", None) 361 rotation_config = attrs.pop("rotation_config", None) 362 363 # Simply fall back to android_binary if no feature splits or bundle_config 364 if not attrs.get("feature_modules", None) and not (attrs.get("bundle_config", None) or attrs.get("bundle_config_file", None)): 365 _android_binary(**attrs) 366 return 367 368 _verify_attrs(attrs, fqn) 369 370 # Create an android_binary base split, plus an android_application to produce the aab 371 name = attrs.pop("name") 372 base_split_name = "%s_base" % name 373 374 # default to [] if feature_modules = None is passed 375 feature_modules = attrs.pop("feature_modules", []) or [] 376 bundle_config = attrs.pop("bundle_config", None) 377 bundle_config_file = attrs.pop("bundle_config_file", None) 378 379 # bundle_config is deprecated in favor of bundle_config_file 380 # In the future bundle_config will accept a build rule rather than a raw file. 381 bundle_config_file = bundle_config_file or bundle_config 382 383 for feature_module in feature_modules: 384 if not feature_module.startswith("//") or ":" not in feature_module: 385 _log.error("feature_modules expects fully qualified paths, i.e. //some/path:target") 386 module_targets = get_feature_module_paths(feature_module) 387 attrs["deps"].append(str(module_targets.title_lib)) 388 389 _android_binary( 390 name = base_split_name, 391 **attrs 392 ) 393 394 android_application( 395 name = name, 396 base_module = ":%s" % base_split_name, 397 bundle_config_file = bundle_config_file, 398 app_integrity_config = app_integrity_config, 399 rotation_config = rotation_config, 400 custom_package = attrs.get("custom_package", None), 401 testonly = attrs.get("testonly"), 402 transitive_configs = attrs.get("transitive_configs", []), 403 feature_modules = feature_modules, 404 application_id = attrs["manifest_values"]["applicationId"], 405 visibility = attrs.get("visibility", None), 406 ) 407