diff options
Diffstat (limited to 'apexer/apexer.py')
-rw-r--r-- | apexer/apexer.py | 208 |
1 files changed, 123 insertions, 85 deletions
diff --git a/apexer/apexer.py b/apexer/apexer.py index e09e053..2fe2b7f 100644 --- a/apexer/apexer.py +++ b/apexer/apexer.py @@ -13,10 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -""" -apexer is a command line tool for creating an APEX file, a package format -for system components. +"""apexer is a command line tool for creating an APEX file, a package format for system components. Typical usage: apexer input_dir output.apex @@ -38,54 +35,81 @@ from apex_manifest import ApexManifestError tool_path_list = None BLOCK_SIZE = 4096 + def ParseArgs(argv): parser = argparse.ArgumentParser(description='Create an APEX file') - parser.add_argument('-f', '--force', action='store_true', - help='force overwriting output') - parser.add_argument('-v', '--verbose', action='store_true', - help='verbose execution') - parser.add_argument('--manifest', default='apex_manifest.json', - help='path to the APEX manifest file') - parser.add_argument('--android_manifest', - help='path to the AndroidManifest file. If omitted, a default one is created and used') - parser.add_argument('--file_contexts', - help='selinux file contexts file. Required for "image" APEXs.') - parser.add_argument('--canned_fs_config', - help='canned_fs_config specifies uid/gid/mode of files. Required for ' + - '"image" APEXS.') - parser.add_argument('--key', - help='path to the private key file. Required for "image" APEXs.') - parser.add_argument('--pubkey', - help='path to the public key file. Used to bundle the public key in APEX for testing.') - parser.add_argument('input_dir', metavar='INPUT_DIR', - help='the directory having files to be packaged') - parser.add_argument('output', metavar='OUTPUT', - help='name of the APEX file') - parser.add_argument('--payload_type', metavar='TYPE', required=False, default="image", - choices=["zip", "image"], - help='type of APEX payload being built "zip" or "image"') - parser.add_argument('--override_apk_package_name', required=False, - help='package name of the APK container. Default is the apex name in --manifest.') - parser.add_argument('--android_jar_path', required=False, - default="prebuilts/sdk/current/public/android.jar", - help='path to use as the source of the android API.') - apexer_path_in_environ = "APEXER_TOOL_PATH" in os.environ - parser.add_argument('--apexer_tool_path', required=not apexer_path_in_environ, - default=os.environ['APEXER_TOOL_PATH'].split(":") if apexer_path_in_environ else None, - type=lambda s: s.split(":"), - help="""A list of directories containing all the tools used by apexer (e.g. + parser.add_argument( + '-f', '--force', action='store_true', help='force overwriting output') + parser.add_argument( + '-v', '--verbose', action='store_true', help='verbose execution') + parser.add_argument( + '--manifest', + default='apex_manifest.json', + help='path to the APEX manifest file') + parser.add_argument( + '--android_manifest', + help='path to the AndroidManifest file. If omitted, a default one is created and used' + ) + parser.add_argument( + '--file_contexts', + help='selinux file contexts file. Required for "image" APEXs.') + parser.add_argument( + '--canned_fs_config', + help='canned_fs_config specifies uid/gid/mode of files. Required for ' + + '"image" APEXS.') + parser.add_argument( + '--key', help='path to the private key file. Required for "image" APEXs.') + parser.add_argument( + '--pubkey', + help='path to the public key file. Used to bundle the public key in APEX for testing.' + ) + parser.add_argument( + 'input_dir', + metavar='INPUT_DIR', + help='the directory having files to be packaged') + parser.add_argument('output', metavar='OUTPUT', help='name of the APEX file') + parser.add_argument( + '--payload_type', + metavar='TYPE', + required=False, + default='image', + choices=['zip', 'image'], + help='type of APEX payload being built "zip" or "image"') + parser.add_argument( + '--override_apk_package_name', + required=False, + help='package name of the APK container. Default is the apex name in --manifest.' + ) + parser.add_argument( + '--android_jar_path', + required=False, + default='prebuilts/sdk/current/public/android.jar', + help='path to use as the source of the android API.') + apexer_path_in_environ = 'APEXER_TOOL_PATH' in os.environ + parser.add_argument( + '--apexer_tool_path', + required=not apexer_path_in_environ, + default=os.environ['APEXER_TOOL_PATH'].split(':') + if apexer_path_in_environ else None, + type=lambda s: s.split(':'), + help="""A list of directories containing all the tools used by apexer (e.g. mke2fs, avbtool, etc.) separated by ':'. Can also be set using the APEXER_TOOL_PATH environment variable""") - parser.add_argument('--target_sdk_version', required=False, - help='Default target SDK version to use for AndroidManifest.xml') + parser.add_argument( + '--target_sdk_version', + required=False, + help='Default target SDK version to use for AndroidManifest.xml') return parser.parse_args(argv) + def FindBinaryPath(binary): for path in tool_path_list: binary_path = os.path.join(path, binary) if os.path.exists(binary_path): return binary_path - raise Exception("Failed to find binary " + binary + " in path " + ":".join(tool_path_list)) + raise Exception('Failed to find binary ' + binary + ' in path ' + + ':'.join(tool_path_list)) + def RunCommand(cmd, verbose=False, env=None): env = env or {} @@ -94,7 +118,7 @@ def RunCommand(cmd, verbose=False, env=None): cmd[0] = FindBinaryPath(cmd[0]) if verbose: - print("Running: " + " ".join(cmd)) + print('Running: ' + ' '.join(cmd)) p = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) output, _ = p.communicate() @@ -102,10 +126,11 @@ def RunCommand(cmd, verbose=False, env=None): if verbose or p.returncode is not 0: print(output.rstrip()) - assert p.returncode is 0, "Failed to execute: " + " ".join(cmd) + assert p.returncode is 0, 'Failed to execute: ' + ' '.join(cmd) return (output, p.returncode) + def GetDirSize(dir_name): size = 0 for dirpath, _, filenames in os.walk(dir_name): @@ -114,16 +139,19 @@ def GetDirSize(dir_name): size += RoundUp(os.path.getsize(os.path.join(dirpath, f)), BLOCK_SIZE) return size + def GetFilesAndDirsCount(dir_name): - count = 0; + count = 0 for root, dirs, files in os.walk(dir_name): count += (len(dirs) + len(files)) return count + def RoundUp(size, unit): assert unit & (unit - 1) == 0 return (size + unit - 1) & (~(unit - 1)) + def PrepareAndroidManifest(package, version): template = """\ <?xml version="1.0" encoding="utf-8"?> @@ -135,13 +163,16 @@ def PrepareAndroidManifest(package, version): """ return template.format(package=package, version=version) + def ValidateAndroidManifest(package, android_manifest): tree = ET.parse(android_manifest) manifest_tag = tree.getroot() package_in_xml = manifest_tag.attrib['package'] if package_in_xml != package: - raise Exception("Package name '" + package_in_xml + "' in '" + android_manifest + - " differ from package name '" + package + "' in the apex_manifest.json") + raise Exception("Package name '" + package_in_xml + "' in '" + + android_manifest + " differ from package name '" + package + + "' in the apex_manifest.json") + def ValidateArgs(args): if not os.path.exists(args.manifest): @@ -154,11 +185,13 @@ def ValidateArgs(args): if args.android_manifest is not None: if not os.path.exists(args.android_manifest): - print("Android Manifest file '" + args.android_manifest + "' does not exist") + print("Android Manifest file '" + args.android_manifest + + "' does not exist") return False if not os.path.isfile(args.android_manifest): - print("Android Manifest file '" + args.android_manifest + "' is not a file") + print("Android Manifest file '" + args.android_manifest + + "' is not a file") return False if not os.path.exists(args.input_dir): @@ -173,30 +206,31 @@ def ValidateArgs(args): print(args.output + ' already exists. Use --force to overwrite.') return False - if args.payload_type == "image": + if args.payload_type == 'image': if not args.key: - print("Missing --key {keyfile} argument!") + print('Missing --key {keyfile} argument!') return False if not args.file_contexts: - print("Missing --file_contexts {contexts} argument!") + print('Missing --file_contexts {contexts} argument!') return False if not args.canned_fs_config: - print("Missing --canned_fs_config {config} argument!") + print('Missing --canned_fs_config {config} argument!') return False return True + def CreateApex(args, work_dir): if not ValidateArgs(args): return False if args.verbose: - print "Using tools from " + str(tool_path_list) + print 'Using tools from ' + str(tool_path_list) try: - with open(args.manifest, "r") as f: + with open(args.manifest, 'r') as f: manifest_raw = f.read() manifest_apex = ValidateApexManifest(manifest_raw) except ApexManifestError as err: @@ -209,7 +243,7 @@ def CreateApex(args, work_dir): # create an empty ext4 image that is sufficiently big # sufficiently big = size + 16MB margin - size_in_mb = (GetDirSize(args.input_dir) / (1024*1024)) + 16 + size_in_mb = (GetDirSize(args.input_dir) / (1024 * 1024)) + 16 content_dir = os.path.join(work_dir, 'content') os.mkdir(content_dir) @@ -228,7 +262,8 @@ def CreateApex(args, work_dir): key_name = os.path.basename(os.path.splitext(args.key)[0]) if manifest_apex.name != key_name: - print("package name '" + manifest_apex.name + "' does not match with key name '" + key_name + "'") + print("package name '" + manifest_apex.name + + "' does not match with key name '" + key_name + "'") return False img_file = os.path.join(content_dir, 'apex_payload.img') @@ -241,18 +276,18 @@ def CreateApex(args, work_dir): inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin cmd = ['mke2fs'] - cmd.extend(['-O', '^has_journal']) # because image is read-only + cmd.extend(['-O', '^has_journal']) # because image is read-only cmd.extend(['-b', str(BLOCK_SIZE)]) - cmd.extend(['-m', '0']) # reserved block percentage + cmd.extend(['-m', '0']) # reserved block percentage cmd.extend(['-t', 'ext4']) - cmd.extend(['-I', '256']) # inode size + cmd.extend(['-I', '256']) # inode size cmd.extend(['-N', str(inode_num)]) - uu = str(uuid.uuid5(uuid.NAMESPACE_URL, "www.android.com")) + uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com')) cmd.extend(['-U', uu]) cmd.extend(['-E', 'hash_seed=' + uu]) cmd.append(img_file) cmd.append(str(size_in_mb) + 'M') - RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"}) + RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'}) # Compile the file context into the binary form compiled_file_contexts = os.path.join(work_dir, 'file_contexts.bin') @@ -263,38 +298,37 @@ def CreateApex(args, work_dir): # Add files to the image file cmd = ['e2fsdroid'] - cmd.append('-e') # input is not android_sparse_file + cmd.append('-e') # input is not android_sparse_file cmd.extend(['-f', args.input_dir]) - cmd.extend(['-T', '0']) # time is set to epoch + cmd.extend(['-T', '0']) # time is set to epoch cmd.extend(['-S', compiled_file_contexts]) cmd.extend(['-C', args.canned_fs_config]) - cmd.append('-s') # share dup blocks + cmd.append('-s') # share dup blocks cmd.append(img_file) - RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"}) + RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'}) cmd = ['e2fsdroid'] - cmd.append('-e') # input is not android_sparse_file + cmd.append('-e') # input is not android_sparse_file cmd.extend(['-f', manifests_dir]) - cmd.extend(['-T', '0']) # time is set to epoch + cmd.extend(['-T', '0']) # time is set to epoch cmd.extend(['-S', compiled_file_contexts]) cmd.extend(['-C', args.canned_fs_config]) - cmd.append('-s') # share dup blocks + cmd.append('-s') # share dup blocks cmd.append(img_file) - RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"}) + RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'}) # Resize the image file to save space cmd = ['resize2fs'] - cmd.append('-M') # shrink as small as possible + cmd.append('-M') # shrink as small as possible cmd.append(img_file) - RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"}) - + RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'}) cmd = ['avbtool'] cmd.append('add_hashtree_footer') cmd.append('--do_not_generate_fec') cmd.extend(['--algorithm', 'SHA256_RSA4096']) cmd.extend(['--key', args.key]) - cmd.extend(['--prop', "apex.key:" + key_name]) + cmd.extend(['--prop', 'apex.key:' + key_name]) # Set up the salt based on manifest content which includes name # and version salt = hashlib.sha256(manifest_raw).hexdigest() @@ -304,10 +338,12 @@ def CreateApex(args, work_dir): # Get the minimum size of the partition required. # TODO(b/113320014) eliminate this step - info, _ = RunCommand(['avbtool', 'info_image', '--image', img_file], args.verbose) + info, _ = RunCommand(['avbtool', 'info_image', '--image', img_file], + args.verbose) vbmeta_offset = int(re.search('VBMeta\ offset:\ *([0-9]+)', info).group(1)) vbmeta_size = int(re.search('VBMeta\ size:\ *([0-9]+)', info).group(1)) - partition_size = RoundUp(vbmeta_offset + vbmeta_size, BLOCK_SIZE) + BLOCK_SIZE + partition_size = RoundUp(vbmeta_offset + vbmeta_size, + BLOCK_SIZE) + BLOCK_SIZE # Resize to the minimum size # TODO(b/113320014) eliminate this step @@ -341,11 +377,12 @@ def CreateApex(args, work_dir): # copy manifest to the content dir so that it is also accessible # without mounting the image - shutil.copyfile(args.manifest, os.path.join(content_dir, 'apex_manifest.json')) + shutil.copyfile(args.manifest, os.path.join(content_dir, + 'apex_manifest.json')) # copy the public key, if specified if args.pubkey: - shutil.copyfile(args.pubkey, os.path.join(content_dir, "apex_pubkey")) + shutil.copyfile(args.pubkey, os.path.join(content_dir, 'apex_pubkey')) apk_file = os.path.join(work_dir, 'apex.apk') cmd = ['aapt2'] @@ -368,21 +405,21 @@ def CreateApex(args, work_dir): zip_file = os.path.join(work_dir, 'apex.zip') cmd = ['soong_zip'] - cmd.append('-d') # include directories - cmd.extend(['-C', content_dir]) # relative root - cmd.extend(['-D', content_dir]) # input dir + cmd.append('-d') # include directories + cmd.extend(['-C', content_dir]) # relative root + cmd.extend(['-D', content_dir]) # input dir for file_ in os.listdir(content_dir): if os.path.isfile(os.path.join(content_dir, file_)): - cmd.extend(['-s', file_]) # don't compress any files + cmd.extend(['-s', file_]) # don't compress any files cmd.extend(['-o', zip_file]) RunCommand(cmd, args.verbose) unaligned_apex_file = os.path.join(work_dir, 'unaligned.apex') cmd = ['merge_zips'] - cmd.append('-j') # sort - cmd.append(unaligned_apex_file) # output - cmd.append(apk_file) # input - cmd.append(zip_file) # input + cmd.append('-j') # sort + cmd.append(unaligned_apex_file) # output + cmd.append(apk_file) # input + cmd.append(zip_file) # input RunCommand(cmd, args.verbose) # Align the files at page boundary for efficient access @@ -400,6 +437,7 @@ def CreateApex(args, work_dir): class TempDirectory(object): + def __enter__(self): self.name = tempfile.mkdtemp() return self.name |