1# Copyright 2020 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"""Bazel Bundletool Commands.""" 16 17load( 18 "//rules:utils.bzl", 19 "ANDROID_TOOLCHAIN_TYPE", 20) 21load("@bazel_skylib//lib:paths.bzl", "paths") 22load(":common.bzl", _common = "common") 23load(":java.bzl", _java = "java") 24 25_density_mapping = { 26 "ldpi": 120, 27 "mdpi": 160, 28 "hdpi": 240, 29 "xhdpi": 320, 30 "xxhdpi": 480, 31 "xxxhdpi": 640, 32 "tvdpi": 213, 33} 34 35def _build( 36 ctx, 37 out = None, 38 modules = [], 39 config = None, 40 metadata = dict(), 41 bundletool = None, 42 host_javabase = None): 43 args = ctx.actions.args() 44 args.add("build-bundle") 45 args.add("--output", out) 46 if modules: 47 args.add_joined("--modules", modules, join_with = ",") 48 if config: 49 args.add("--config", config) 50 for path, f in metadata.items(): 51 args.add("--metadata-file", "%s:%s" % (path, f.path)) 52 53 _java.run( 54 ctx = ctx, 55 host_javabase = host_javabase, 56 executable = bundletool, 57 arguments = [args], 58 inputs = ( 59 modules + 60 ([config] if config else []) + 61 metadata.values() 62 ), 63 outputs = [out], 64 mnemonic = "BuildBundle", 65 progress_message = "Building bundle %s" % out.short_path, 66 ) 67 68def _build_device_json( 69 ctx, 70 out, 71 abis, 72 locales, 73 density, 74 sdk_version): 75 json_content = json.encode(struct( 76 supportedAbis = abis, 77 supportedLocales = locales, 78 screenDensity = _density_mapping[density], 79 sdkVersion = int(sdk_version), 80 )) 81 ctx.actions.write(out, json_content) 82 83def _build_sdk_apks( 84 ctx, 85 out = None, 86 aapt2 = None, 87 sdk_bundle = None, 88 debug_key = None, 89 bundletool = None, 90 host_javabase = None): 91 apks_out = ctx.actions.declare_directory( 92 "%s_apks_out" % paths.basename(out.path).replace(".", "_"), 93 sibling = out, 94 ) 95 args = ctx.actions.args() 96 args.add("build-sdk-apks") 97 args.add("--aapt2", aapt2.executable.path) 98 args.add("--sdk-bundle", sdk_bundle) 99 args.add("--ks", debug_key) 100 args.add("--ks-pass=pass:android") 101 args.add("--ks-key-alias=androiddebugkey") 102 args.add("--key-pass=pass:android") 103 args.add("--output-format=DIRECTORY") 104 args.add("--output", apks_out.path) 105 _java.run( 106 ctx = ctx, 107 host_javabase = host_javabase, 108 executable = bundletool, 109 arguments = [args], 110 inputs = [ 111 sdk_bundle, 112 debug_key, 113 ], 114 tools = [aapt2], 115 outputs = [apks_out], 116 mnemonic = "BuildSdkApksDir", 117 progress_message = "Building SDK APKs directory %s" % apks_out.short_path, 118 ) 119 120 # Now move standalone APK out of bundletool output dir. 121 ctx.actions.run_shell( 122 command = """ 123set -e 124APKS_OUT_DIR=%s 125DEBUG_APK_PATH=%s 126 127mv "${APKS_OUT_DIR}/standalones/standalone.apk" "${DEBUG_APK_PATH}" 128""" % ( 129 apks_out.path, 130 out.path, 131 ), 132 tools = [], 133 arguments = [], 134 inputs = [apks_out], 135 outputs = [out], 136 mnemonic = "ExtractDebugSdkApk", 137 progress_message = "Extract debug SDK APK to %s" % out.short_path, 138 ) 139 140def _build_sdk_bundle( 141 ctx, 142 out = None, 143 module = None, 144 sdk_api_descriptors = None, 145 sdk_modules_config = None, 146 bundletool = None, 147 host_javabase = None): 148 args = ctx.actions.args() 149 args.add("build-sdk-bundle") 150 151 args.add("--sdk-modules-config", sdk_modules_config) 152 args.add("--sdk-interface-descriptors", sdk_api_descriptors) 153 args.add("--modules", module) 154 args.add("--output", out) 155 _java.run( 156 ctx = ctx, 157 host_javabase = host_javabase, 158 executable = bundletool, 159 arguments = [args], 160 inputs = [ 161 module, 162 sdk_api_descriptors, 163 sdk_modules_config, 164 ], 165 outputs = [out], 166 mnemonic = "BuildASB", 167 progress_message = "Building SDK bundle %s" % out.short_path, 168 ) 169 170def _build_sdk_module( 171 ctx, 172 out = None, 173 internal_apk = None, 174 bundletool_module_builder = None, 175 host_javabase = None): 176 args = ctx.actions.args() 177 args.add("--internal_apk_path", internal_apk) 178 args.add("--output_module_path", out) 179 ctx.actions.run( 180 inputs = [internal_apk], 181 outputs = [out], 182 executable = bundletool_module_builder, 183 arguments = [args], 184 mnemonic = "BuildSdkModule", 185 progress_message = "Building ASB zip module %s" % out.short_path, 186 toolchain = ANDROID_TOOLCHAIN_TYPE, 187 ) 188 189def _bundle_to_apks( 190 ctx, 191 out = None, 192 bundle = None, 193 mode = None, 194 system_apk_options = None, 195 device_spec = None, 196 keystore = None, 197 oldest_signer = None, 198 lineage = None, 199 rotation_min_sdk = None, 200 modules = None, 201 aapt2 = None, 202 bundletool = None, 203 host_javabase = None): 204 inputs = [bundle] 205 args = ctx.actions.args() 206 args.add("build-apks") 207 args.add("--output", out) 208 args.add("--bundle", bundle) 209 args.add("--aapt2", aapt2.executable.path) 210 211 if mode: 212 args.add("--mode", mode) 213 214 if system_apk_options: 215 if mode != "SYSTEM": 216 fail("Unexpected system_apk_options specified, requires SYSTEM mode but got %s" % mode) 217 args.add_joined("--system-apk-options", system_apk_options, join_with = ",") 218 219 if keystore: 220 args.add("--ks", keystore.path) 221 args.add("--ks-pass", "pass:android") 222 args.add("--ks-key-alias", "AndroidDebugKey") 223 inputs.append(keystore) 224 225 if lineage: 226 if not oldest_signer: 227 fail("Key rotation requires oldest_signer in %s" % ctx.label) 228 oldest_signer_properties = _common.create_signer_properties(ctx, oldest_signer) 229 args.add("--oldest-signer", oldest_signer_properties.path) 230 args.add("--lineage", lineage.short_path) 231 inputs.append(oldest_signer_properties) 232 inputs.append(oldest_signer) 233 inputs.append(lineage) 234 235 if rotation_min_sdk: 236 args.add("--rotation-min-sdk-version", rotation_min_sdk) 237 238 if device_spec: 239 args.add("--device-spec", device_spec) 240 inputs.append(device_spec) 241 242 if modules: 243 args.add_joined("--modules", modules, join_with = ",") 244 245 _java.run( 246 ctx = ctx, 247 host_javabase = host_javabase, 248 executable = bundletool, 249 arguments = [args], 250 inputs = inputs, 251 outputs = [out], 252 tools = [aapt2], 253 mnemonic = "BundleToApks", 254 progress_message = "Converting bundle to .apks: %s" % out.short_path, 255 ) 256 257def _extract_config( 258 ctx, 259 out = None, 260 aab = None, 261 bundletool = None, 262 host_javabase = None): 263 # Need to execute as a shell script as the tool outputs to stdout 264 cmd = """ 265set -e 266contents=`%s -jar %s dump config --bundle %s` 267echo "$contents" > %s 268""" % ( 269 host_javabase[java_common.JavaRuntimeInfo].java_executable_exec_path, 270 bundletool.executable.path, 271 aab.path, 272 out.path, 273 ) 274 275 ctx.actions.run_shell( 276 inputs = [aab], 277 outputs = [out], 278 tools = depset([bundletool.executable], transitive = [host_javabase[java_common.JavaRuntimeInfo].files]), 279 mnemonic = "ExtractBundleConfig", 280 progress_message = "Extract bundle config to %s" % out.short_path, 281 command = cmd, 282 exec_group = "android_and_java", 283 ) 284 285def _extract_manifest( 286 ctx, 287 out = None, 288 aab = None, 289 module = None, 290 xpath = None, 291 bundletool = None, 292 host_javabase = None): 293 # Need to execute as a shell script as the tool outputs to stdout 294 extra_flags = [] 295 if module: 296 extra_flags.append("--module " + module) 297 if xpath: 298 extra_flags.append("--xpath " + xpath) 299 cmd = """ 300set -e 301contents=`%s -jar %s dump manifest --bundle %s %s` 302echo "$contents" > %s 303""" % ( 304 host_javabase[java_common.JavaRuntimeInfo].java_executable_exec_path, 305 bundletool.executable.path, 306 aab.path, 307 " ".join(extra_flags), 308 out.path, 309 ) 310 311 ctx.actions.run_shell( 312 inputs = [aab], 313 outputs = [out], 314 tools = depset([bundletool.executable], transitive = [host_javabase[java_common.JavaRuntimeInfo].files]), 315 mnemonic = "ExtractBundleManifest", 316 progress_message = "Extract bundle manifest to %s" % out.short_path, 317 command = cmd, 318 exec_group = "android_and_java", 319 ) 320 321def _proto_apk_to_module( 322 ctx, 323 out = None, 324 proto_apk = None, 325 bundletool_module_builder = None): 326 args = ctx.actions.args() 327 args.add("--internal_apk_path", proto_apk) 328 args.add("--output_module_path", out) 329 ctx.actions.run( 330 inputs = [proto_apk], 331 outputs = [out], 332 executable = bundletool_module_builder, 333 arguments = [args], 334 mnemonic = "BuildAppModule", 335 progress_message = "Building AAB zip module %s" % out.short_path, 336 toolchain = ANDROID_TOOLCHAIN_TYPE, 337 ) 338 339bundletool = struct( 340 build = _build, 341 build_device_json = _build_device_json, 342 build_sdk_apks = _build_sdk_apks, 343 build_sdk_bundle = _build_sdk_bundle, 344 build_sdk_module = _build_sdk_module, 345 bundle_to_apks = _bundle_to_apks, 346 extract_config = _extract_config, 347 extract_manifest = _extract_manifest, 348 proto_apk_to_module = _proto_apk_to_module, 349) 350