1#!/usr/bin/env python 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16"""apexer is a command line tool for creating an APEX file, a package format for system components. 17 18Typical usage: apexer input_dir output.apex 19 20""" 21 22import apex_build_info_pb2 23import argparse 24import hashlib 25import os 26import re 27import shlex 28import shutil 29import subprocess 30import sys 31import tempfile 32import uuid 33import xml.etree.ElementTree as ET 34from apex_manifest import ValidateApexManifest 35from apex_manifest import ApexManifestError 36from manifest import android_ns 37from manifest import find_child_with_attribute 38from manifest import get_children_with_tag 39from manifest import get_indent 40from manifest import parse_manifest 41from manifest import write_xml 42from xml.dom import minidom 43 44tool_path_list = None 45BLOCK_SIZE = 4096 46 47 48def ParseArgs(argv): 49 parser = argparse.ArgumentParser(description='Create an APEX file') 50 parser.add_argument( 51 '-f', '--force', action='store_true', help='force overwriting output') 52 parser.add_argument( 53 '-v', '--verbose', action='store_true', help='verbose execution') 54 parser.add_argument( 55 '--manifest', 56 default='apex_manifest.pb', 57 help='path to the APEX manifest file (.pb)') 58 parser.add_argument( 59 '--manifest_json', 60 required=False, 61 help='path to the APEX manifest file (Q compatible .json)') 62 parser.add_argument( 63 '--android_manifest', 64 help='path to the AndroidManifest file. If omitted, a default one is created and used' 65 ) 66 parser.add_argument( 67 '--logging_parent', 68 help=('specify logging parent as an additional <meta-data> tag.' 69 'This value is ignored if the logging_parent meta-data tag is present.')) 70 parser.add_argument( 71 '--assets_dir', 72 help='an assets directory to be included in the APEX' 73 ) 74 parser.add_argument( 75 '--file_contexts', 76 help='selinux file contexts file. Required for "image" APEXs.') 77 parser.add_argument( 78 '--canned_fs_config', 79 help='canned_fs_config specifies uid/gid/mode of files. Required for ' + 80 '"image" APEXS.') 81 parser.add_argument( 82 '--key', help='path to the private key file. Required for "image" APEXs.') 83 parser.add_argument( 84 '--pubkey', 85 help='path to the public key file. Used to bundle the public key in APEX for testing.' 86 ) 87 parser.add_argument( 88 '--signing_args', 89 help='the extra signing arguments passed to avbtool. Used for "image" APEXs.' 90 ) 91 parser.add_argument( 92 'input_dir', 93 metavar='INPUT_DIR', 94 help='the directory having files to be packaged') 95 parser.add_argument('output', metavar='OUTPUT', help='name of the APEX file') 96 parser.add_argument( 97 '--payload_type', 98 metavar='TYPE', 99 required=False, 100 default='image', 101 choices=['zip', 'image'], 102 help='type of APEX payload being built "zip" or "image"') 103 parser.add_argument( 104 '--override_apk_package_name', 105 required=False, 106 help='package name of the APK container. Default is the apex name in --manifest.' 107 ) 108 parser.add_argument( 109 '--no_hashtree', 110 required=False, 111 action='store_true', 112 help='hashtree is omitted from "image".' 113 ) 114 parser.add_argument( 115 '--android_jar_path', 116 required=False, 117 default='prebuilts/sdk/current/public/android.jar', 118 help='path to use as the source of the android API.') 119 apexer_path_in_environ = 'APEXER_TOOL_PATH' in os.environ 120 parser.add_argument( 121 '--apexer_tool_path', 122 required=not apexer_path_in_environ, 123 default=os.environ['APEXER_TOOL_PATH'].split(':') 124 if apexer_path_in_environ else None, 125 type=lambda s: s.split(':'), 126 help="""A list of directories containing all the tools used by apexer (e.g. 127 mke2fs, avbtool, etc.) separated by ':'. Can also be set using the 128 APEXER_TOOL_PATH environment variable""") 129 parser.add_argument( 130 '--target_sdk_version', 131 required=False, 132 help='Default target SDK version to use for AndroidManifest.xml') 133 parser.add_argument( 134 '--min_sdk_version', 135 required=False, 136 help='Default Min SDK version to use for AndroidManifest.xml') 137 parser.add_argument( 138 '--do_not_check_keyname', 139 required=False, 140 action='store_true', 141 help='Do not check key name. Use the name of apex instead of the basename of --key.') 142 parser.add_argument( 143 '--include_build_info', 144 required=False, 145 action='store_true', 146 help='Include build information file in the resulting apex.') 147 parser.add_argument( 148 '--include_cmd_line_in_build_info', 149 required=False, 150 action='store_true', 151 help='Include the command line in the build information file in the resulting apex. ' 152 'Note that this makes it harder to make deterministic builds.') 153 parser.add_argument( 154 '--build_info', 155 required=False, 156 help='Build information file to be used for default values.') 157 parser.add_argument( 158 '--payload_only', 159 action='store_true', 160 help='Outputs the payload image/zip only.' 161 ) 162 parser.add_argument( 163 '--unsigned_payload_only', 164 action='store_true', 165 help="""Outputs the unsigned payload image/zip only. Also, setting this flag implies 166 --payload_only is set too.""" 167 ) 168 parser.add_argument( 169 '--unsigned_payload', 170 action='store_true', 171 help="""Skip signing the apex payload. Used only for testing purposes.""" 172 ) 173 return parser.parse_args(argv) 174 175 176def FindBinaryPath(binary): 177 for path in tool_path_list: 178 binary_path = os.path.join(path, binary) 179 if os.path.exists(binary_path): 180 return binary_path 181 raise Exception('Failed to find binary ' + binary + ' in path ' + 182 ':'.join(tool_path_list)) 183 184 185def RunCommand(cmd, verbose=False, env=None): 186 env = env or {} 187 env.update(os.environ.copy()) 188 189 cmd[0] = FindBinaryPath(cmd[0]) 190 191 if verbose: 192 print('Running: ' + ' '.join(cmd)) 193 p = subprocess.Popen( 194 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) 195 output, _ = p.communicate() 196 197 if verbose or p.returncode is not 0: 198 print(output.rstrip()) 199 200 assert p.returncode is 0, 'Failed to execute: ' + ' '.join(cmd) 201 202 return (output, p.returncode) 203 204 205def GetDirSize(dir_name): 206 size = 0 207 for dirpath, _, filenames in os.walk(dir_name): 208 size += RoundUp(os.path.getsize(dirpath), BLOCK_SIZE) 209 for f in filenames: 210 path = os.path.join(dirpath, f) 211 if not os.path.isfile(path): 212 continue 213 size += RoundUp(os.path.getsize(path), BLOCK_SIZE) 214 return size 215 216 217def GetFilesAndDirsCount(dir_name): 218 count = 0 219 for root, dirs, files in os.walk(dir_name): 220 count += (len(dirs) + len(files)) 221 return count 222 223 224def RoundUp(size, unit): 225 assert unit & (unit - 1) == 0 226 return (size + unit - 1) & (~(unit - 1)) 227 228 229def PrepareAndroidManifest(package, version): 230 template = """\ 231<?xml version="1.0" encoding="utf-8"?> 232<manifest xmlns:android="http://schemas.android.com/apk/res/android" 233 package="{package}" android:versionCode="{version}"> 234 <!-- APEX does not have classes.dex --> 235 <application android:hasCode="false" /> 236</manifest> 237""" 238 return template.format(package=package, version=version) 239 240 241def ValidateAndroidManifest(package, android_manifest): 242 tree = ET.parse(android_manifest) 243 manifest_tag = tree.getroot() 244 package_in_xml = manifest_tag.attrib['package'] 245 if package_in_xml != package: 246 raise Exception("Package name '" + package_in_xml + "' in '" + 247 android_manifest + " differ from package name '" + package + 248 "' in the apex_manifest.pb") 249 250 251def ValidateArgs(args): 252 build_info = None 253 254 if args.build_info is not None: 255 if not os.path.exists(args.build_info): 256 print("Build info file '" + args.build_info + "' does not exist") 257 return False 258 with open(args.build_info) as buildInfoFile: 259 build_info = apex_build_info_pb2.ApexBuildInfo() 260 build_info.ParseFromString(buildInfoFile.read()) 261 262 if not os.path.exists(args.manifest): 263 print("Manifest file '" + args.manifest + "' does not exist") 264 return False 265 266 if not os.path.isfile(args.manifest): 267 print("Manifest file '" + args.manifest + "' is not a file") 268 return False 269 270 if args.android_manifest is not None: 271 if not os.path.exists(args.android_manifest): 272 print("Android Manifest file '" + args.android_manifest + 273 "' does not exist") 274 return False 275 276 if not os.path.isfile(args.android_manifest): 277 print("Android Manifest file '" + args.android_manifest + 278 "' is not a file") 279 return False 280 elif build_info is not None: 281 with tempfile.NamedTemporaryFile(delete=False) as temp: 282 temp.write(build_info.android_manifest) 283 args.android_manifest = temp.name 284 285 if not os.path.exists(args.input_dir): 286 print("Input directory '" + args.input_dir + "' does not exist") 287 return False 288 289 if not os.path.isdir(args.input_dir): 290 print("Input directory '" + args.input_dir + "' is not a directory") 291 return False 292 293 if not args.force and os.path.exists(args.output): 294 print(args.output + ' already exists. Use --force to overwrite.') 295 return False 296 297 if args.unsigned_payload_only: 298 args.payload_only = True; 299 args.unsigned_payload = True; 300 301 if args.payload_type == 'image': 302 if not args.key and not args.unsigned_payload: 303 print('Missing --key {keyfile} argument!') 304 return False 305 306 if not args.file_contexts: 307 if build_info is not None: 308 with tempfile.NamedTemporaryFile(delete=False) as temp: 309 temp.write(build_info.file_contexts) 310 args.file_contexts = temp.name 311 else: 312 print('Missing --file_contexts {contexts} argument, or a --build_info argument!') 313 return False 314 315 if not args.canned_fs_config: 316 if not args.canned_fs_config: 317 if build_info is not None: 318 with tempfile.NamedTemporaryFile(delete=False) as temp: 319 temp.write(build_info.canned_fs_config) 320 args.canned_fs_config = temp.name 321 else: 322 print('Missing ----canned_fs_config {config} argument, or a --build_info argument!') 323 return False 324 325 if not args.target_sdk_version: 326 if build_info is not None: 327 if build_info.target_sdk_version: 328 args.target_sdk_version = build_info.target_sdk_version 329 330 if not args.no_hashtree: 331 if build_info is not None: 332 if build_info.no_hashtree: 333 args.no_hashtree = True 334 335 if not args.min_sdk_version: 336 if build_info is not None: 337 if build_info.min_sdk_version: 338 args.min_sdk_version = build_info.min_sdk_version 339 340 if not args.override_apk_package_name: 341 if build_info is not None: 342 if build_info.override_apk_package_name: 343 args.override_apk_package_name = build_info.override_apk_package_name 344 345 if not args.logging_parent: 346 if build_info is not None: 347 if build_info.logging_parent: 348 args.logging_parent = build_info.logging_parent 349 350 return True 351 352def GenerateBuildInfo(args): 353 build_info = apex_build_info_pb2.ApexBuildInfo() 354 if (args.include_cmd_line_in_build_info): 355 build_info.apexer_command_line = str(sys.argv) 356 357 with open(args.file_contexts) as f: 358 build_info.file_contexts = f.read() 359 360 with open(args.canned_fs_config) as f: 361 build_info.canned_fs_config = f.read() 362 363 with open(args.android_manifest) as f: 364 build_info.android_manifest = f.read() 365 366 if args.target_sdk_version: 367 build_info.target_sdk_version = args.target_sdk_version 368 369 if args.min_sdk_version: 370 build_info.min_sdk_version = args.min_sdk_version 371 372 if args.no_hashtree: 373 build_info.no_hashtree = True 374 375 if args.override_apk_package_name: 376 build_info.override_apk_package_name = args.override_apk_package_name 377 378 if args.logging_parent: 379 build_info.logging_parent = args.logging_parent 380 381 return build_info 382 383def AddLoggingParent(android_manifest, logging_parent_value): 384 """Add logging parent as an additional <meta-data> tag. 385 386 Args: 387 android_manifest: A string representing AndroidManifest.xml 388 logging_parent_value: A string representing the logging 389 parent value. 390 Raises: 391 RuntimeError: Invalid manifest 392 Returns: 393 A path to modified AndroidManifest.xml 394 """ 395 doc = minidom.parse(android_manifest) 396 manifest = parse_manifest(doc) 397 logging_parent_key = 'android.content.pm.LOGGING_PARENT' 398 elems = get_children_with_tag(manifest, 'application') 399 application = elems[0] if len(elems) == 1 else None 400 if len(elems) > 1: 401 raise RuntimeError('found multiple <application> tags') 402 elif not elems: 403 application = doc.createElement('application') 404 indent = get_indent(manifest.firstChild, 1) 405 first = manifest.firstChild 406 manifest.insertBefore(doc.createTextNode(indent), first) 407 manifest.insertBefore(application, first) 408 409 indent = get_indent(application.firstChild, 2) 410 last = application.lastChild 411 if last is not None and last.nodeType != minidom.Node.TEXT_NODE: 412 last = None 413 414 if not find_child_with_attribute(application, 'meta-data', android_ns, 415 'name', logging_parent_key): 416 ul = doc.createElement('meta-data') 417 ul.setAttributeNS(android_ns, 'android:name', logging_parent_key) 418 ul.setAttributeNS(android_ns, 'android:value', logging_parent_value) 419 application.insertBefore(doc.createTextNode(indent), last) 420 application.insertBefore(ul, last) 421 last = application.lastChild 422 423 if last and last.nodeType != minidom.Node.TEXT_NODE: 424 indent = get_indent(application.previousSibling, 1) 425 application.appendChild(doc.createTextNode(indent)) 426 427 with tempfile.NamedTemporaryFile(delete=False) as temp: 428 write_xml(temp, doc) 429 return temp.name 430 431def CreateApex(args, work_dir): 432 if not ValidateArgs(args): 433 return False 434 435 if args.verbose: 436 print 'Using tools from ' + str(tool_path_list) 437 438 def copyfile(src, dst): 439 if args.verbose: 440 print('Copying ' + src + ' to ' + dst) 441 shutil.copyfile(src, dst) 442 443 try: 444 manifest_apex = ValidateApexManifest(args.manifest) 445 except ApexManifestError as err: 446 print("'" + args.manifest + "' is not a valid manifest file") 447 print err.errmessage 448 return False 449 except IOError: 450 print("Cannot read manifest file: '" + args.manifest + "'") 451 return False 452 453 # create an empty ext4 image that is sufficiently big 454 # sufficiently big = size + 16MB margin 455 size_in_mb = (GetDirSize(args.input_dir) / (1024 * 1024)) + 16 456 457 content_dir = os.path.join(work_dir, 'content') 458 os.mkdir(content_dir) 459 460 # APEX manifest is also included in the image. The manifest is included 461 # twice: once inside the image and once outside the image (but still 462 # within the zip container). 463 manifests_dir = os.path.join(work_dir, 'manifests') 464 os.mkdir(manifests_dir) 465 copyfile(args.manifest, os.path.join(manifests_dir, 'apex_manifest.pb')) 466 if args.manifest_json: 467 # manifest_json is for compatibility 468 copyfile(args.manifest_json, os.path.join(manifests_dir, 'apex_manifest.json')) 469 470 if args.payload_type == 'image': 471 if args.do_not_check_keyname or args.unsigned_payload: 472 key_name = manifest_apex.name 473 else: 474 key_name = os.path.basename(os.path.splitext(args.key)[0]) 475 476 if manifest_apex.name != key_name: 477 print("package name '" + manifest_apex.name + 478 "' does not match with key name '" + key_name + "'") 479 return False 480 img_file = os.path.join(content_dir, 'apex_payload.img') 481 482 # margin is for files that are not under args.input_dir. this consists of 483 # n inodes for apex_manifest files and 11 reserved inodes for ext4. 484 # TOBO(b/122991714) eliminate these details. use build_image.py which 485 # determines the optimal inode count by first building an image and then 486 # count the inodes actually used. 487 inode_num_margin = GetFilesAndDirsCount(manifests_dir) + 11 488 inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin 489 490 cmd = ['mke2fs'] 491 cmd.extend(['-O', '^has_journal']) # because image is read-only 492 cmd.extend(['-b', str(BLOCK_SIZE)]) 493 cmd.extend(['-m', '0']) # reserved block percentage 494 cmd.extend(['-t', 'ext4']) 495 cmd.extend(['-I', '256']) # inode size 496 cmd.extend(['-N', str(inode_num)]) 497 uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com')) 498 cmd.extend(['-U', uu]) 499 cmd.extend(['-E', 'hash_seed=' + uu]) 500 cmd.append(img_file) 501 cmd.append(str(size_in_mb) + 'M') 502 RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'}) 503 504 # Compile the file context into the binary form 505 compiled_file_contexts = os.path.join(work_dir, 'file_contexts.bin') 506 cmd = ['sefcontext_compile'] 507 cmd.extend(['-o', compiled_file_contexts]) 508 cmd.append(args.file_contexts) 509 RunCommand(cmd, args.verbose) 510 511 # Add files to the image file 512 cmd = ['e2fsdroid'] 513 cmd.append('-e') # input is not android_sparse_file 514 cmd.extend(['-f', args.input_dir]) 515 cmd.extend(['-T', '0']) # time is set to epoch 516 cmd.extend(['-S', compiled_file_contexts]) 517 cmd.extend(['-C', args.canned_fs_config]) 518 cmd.append('-s') # share dup blocks 519 cmd.append(img_file) 520 RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'}) 521 522 cmd = ['e2fsdroid'] 523 cmd.append('-e') # input is not android_sparse_file 524 cmd.extend(['-f', manifests_dir]) 525 cmd.extend(['-T', '0']) # time is set to epoch 526 cmd.extend(['-S', compiled_file_contexts]) 527 cmd.extend(['-C', args.canned_fs_config]) 528 cmd.append('-s') # share dup blocks 529 cmd.append(img_file) 530 RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'}) 531 532 # Resize the image file to save space 533 cmd = ['resize2fs'] 534 cmd.append('-M') # shrink as small as possible 535 cmd.append(img_file) 536 RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'}) 537 538 if args.unsigned_payload_only: 539 shutil.copyfile(img_file, args.output) 540 if (args.verbose): 541 print('Created (unsigned payload only) ' + args.output) 542 return True 543 544 if not args.unsigned_payload: 545 cmd = ['avbtool'] 546 cmd.append('add_hashtree_footer') 547 cmd.append('--do_not_generate_fec') 548 cmd.extend(['--algorithm', 'SHA256_RSA4096']) 549 cmd.extend(['--hash_algorithm', 'sha256']) 550 cmd.extend(['--key', args.key]) 551 cmd.extend(['--prop', 'apex.key:' + key_name]) 552 # Set up the salt based on manifest content which includes name 553 # and version 554 salt = hashlib.sha256(manifest_apex.SerializeToString()).hexdigest() 555 cmd.extend(['--salt', salt]) 556 cmd.extend(['--image', img_file]) 557 if args.no_hashtree: 558 cmd.append('--no_hashtree') 559 if args.signing_args: 560 cmd.extend(shlex.split(args.signing_args)) 561 RunCommand(cmd, args.verbose) 562 563 # Get the minimum size of the partition required. 564 # TODO(b/113320014) eliminate this step 565 info, _ = RunCommand(['avbtool', 'info_image', '--image', img_file], 566 args.verbose) 567 vbmeta_offset = int(re.search('VBMeta\ offset:\ *([0-9]+)', info).group(1)) 568 vbmeta_size = int(re.search('VBMeta\ size:\ *([0-9]+)', info).group(1)) 569 partition_size = RoundUp(vbmeta_offset + vbmeta_size, 570 BLOCK_SIZE) + BLOCK_SIZE 571 572 # Resize to the minimum size 573 # TODO(b/113320014) eliminate this step 574 cmd = ['avbtool'] 575 cmd.append('resize_image') 576 cmd.extend(['--image', img_file]) 577 cmd.extend(['--partition_size', str(partition_size)]) 578 RunCommand(cmd, args.verbose) 579 else: 580 img_file = os.path.join(content_dir, 'apex_payload.zip') 581 cmd = ['soong_zip'] 582 cmd.extend(['-o', img_file]) 583 cmd.extend(['-C', args.input_dir]) 584 cmd.extend(['-D', args.input_dir]) 585 cmd.extend(['-C', manifests_dir]) 586 cmd.extend(['-D', manifests_dir]) 587 RunCommand(cmd, args.verbose) 588 589 if args.payload_only: 590 shutil.copyfile(img_file, args.output) 591 if (args.verbose): 592 print('Created (payload only) ' + args.output) 593 return True 594 595 # package the image file and APEX manifest as an APK. 596 # The AndroidManifest file is automatically generated if not given. 597 android_manifest_file = os.path.join(work_dir, 'AndroidManifest.xml') 598 if not args.android_manifest: 599 if args.verbose: 600 print('Creating AndroidManifest ' + android_manifest_file) 601 with open(android_manifest_file, 'w+') as f: 602 app_package_name = manifest_apex.name 603 f.write(PrepareAndroidManifest(app_package_name, manifest_apex.version)) 604 args.android_manifest = android_manifest_file 605 else: 606 ValidateAndroidManifest(manifest_apex.name, args.android_manifest) 607 shutil.copyfile(args.android_manifest, android_manifest_file) 608 609 # If logging parent is specified, add it to the AndroidManifest. 610 if args.logging_parent != "": 611 android_manifest_file = AddLoggingParent(android_manifest_file, 612 args.logging_parent) 613 614 # copy manifest to the content dir so that it is also accessible 615 # without mounting the image 616 copyfile(args.manifest, os.path.join(content_dir, 'apex_manifest.pb')) 617 if args.manifest_json: 618 copyfile(args.manifest_json, os.path.join(content_dir, 'apex_manifest.json')) 619 620 # copy the public key, if specified 621 if args.pubkey: 622 shutil.copyfile(args.pubkey, os.path.join(content_dir, 'apex_pubkey')) 623 624 if args.include_build_info: 625 build_info = GenerateBuildInfo(args) 626 with open(os.path.join(content_dir, 'apex_build_info.pb'), "wb") as f: 627 f.write(build_info.SerializeToString()) 628 629 apk_file = os.path.join(work_dir, 'apex.apk') 630 cmd = ['aapt2'] 631 cmd.append('link') 632 cmd.extend(['--manifest', android_manifest_file]) 633 if args.override_apk_package_name: 634 cmd.extend(['--rename-manifest-package', args.override_apk_package_name]) 635 # This version from apex_manifest.json is used when versionCode isn't 636 # specified in AndroidManifest.xml 637 cmd.extend(['--version-code', str(manifest_apex.version)]) 638 if manifest_apex.versionName: 639 cmd.extend(['--version-name', manifest_apex.versionName]) 640 if args.target_sdk_version: 641 cmd.extend(['--target-sdk-version', args.target_sdk_version]) 642 if args.min_sdk_version: 643 cmd.extend(['--min-sdk-version', args.min_sdk_version]) 644 else: 645 # Default value for minSdkVersion. 646 cmd.extend(['--min-sdk-version', '29']) 647 if args.assets_dir: 648 cmd.extend(['-A', args.assets_dir]) 649 cmd.extend(['-o', apk_file]) 650 cmd.extend(['-I', args.android_jar_path]) 651 RunCommand(cmd, args.verbose) 652 653 zip_file = os.path.join(work_dir, 'apex.zip') 654 cmd = ['soong_zip'] 655 cmd.append('-d') # include directories 656 cmd.extend(['-C', content_dir]) # relative root 657 cmd.extend(['-D', content_dir]) # input dir 658 for file_ in os.listdir(content_dir): 659 if os.path.isfile(os.path.join(content_dir, file_)): 660 cmd.extend(['-s', file_]) # don't compress any files 661 cmd.extend(['-o', zip_file]) 662 RunCommand(cmd, args.verbose) 663 664 unaligned_apex_file = os.path.join(work_dir, 'unaligned.apex') 665 cmd = ['merge_zips'] 666 cmd.append('-j') # sort 667 cmd.append(unaligned_apex_file) # output 668 cmd.append(apk_file) # input 669 cmd.append(zip_file) # input 670 RunCommand(cmd, args.verbose) 671 672 # Align the files at page boundary for efficient access 673 cmd = ['zipalign'] 674 cmd.append('-f') 675 cmd.append(str(BLOCK_SIZE)) 676 cmd.append(unaligned_apex_file) 677 cmd.append(args.output) 678 RunCommand(cmd, args.verbose) 679 680 if (args.verbose): 681 print('Created ' + args.output) 682 683 return True 684 685 686class TempDirectory(object): 687 688 def __enter__(self): 689 self.name = tempfile.mkdtemp() 690 return self.name 691 692 def __exit__(self, *unused): 693 shutil.rmtree(self.name) 694 695 696def main(argv): 697 global tool_path_list 698 args = ParseArgs(argv) 699 tool_path_list = args.apexer_tool_path 700 with TempDirectory() as work_dir: 701 success = CreateApex(args, work_dir) 702 703 if not success: 704 sys.exit(1) 705 706 707if __name__ == '__main__': 708 main(sys.argv[1:]) 709