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