Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 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 | |
| 17 | """ |
| 18 | apexer is a command line tool for creating an APEX file, a package format |
| 19 | for system components. |
| 20 | |
| 21 | Typical usage: apexer input_dir output.apex |
| 22 | |
| 23 | """ |
| 24 | |
| 25 | import argparse |
Jiyong Park | 3d44bb4 | 2018-12-18 19:44:54 +0900 | [diff] [blame] | 26 | import hashlib |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 27 | import os |
Jiyong Park | d372d3b | 2018-08-28 22:06:56 +0900 | [diff] [blame] | 28 | import re |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 29 | import shutil |
| 30 | import subprocess |
| 31 | import sys |
| 32 | import tempfile |
Jiyong Park | 3d44bb4 | 2018-12-18 19:44:54 +0900 | [diff] [blame] | 33 | import uuid |
Jiyong Park | 1c4605a | 2019-02-08 02:49:59 +0900 | [diff] [blame] | 34 | import xml.etree.ElementTree as ET |
Dario Freni | adbee5d | 2018-12-27 12:44:01 +0000 | [diff] [blame] | 35 | from apex_manifest import ValidateApexManifest |
| 36 | from apex_manifest import ApexManifestError |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 37 | |
Alex Light | 18a1b19 | 2019-03-04 14:27:28 -0800 | [diff] [blame] | 38 | tool_path_list = None |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 39 | BLOCK_SIZE = 4096 |
| 40 | |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 41 | def ParseArgs(argv): |
| 42 | parser = argparse.ArgumentParser(description='Create an APEX file') |
| 43 | parser.add_argument('-f', '--force', action='store_true', |
| 44 | help='force overwriting output') |
| 45 | parser.add_argument('-v', '--verbose', action='store_true', |
| 46 | help='verbose execution') |
Dario Freni | 0a21264 | 2018-11-20 18:07:44 +0000 | [diff] [blame] | 47 | parser.add_argument('--manifest', default='apex_manifest.json', |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 48 | help='path to the APEX manifest file') |
Jiyong Park | 1c4605a | 2019-02-08 02:49:59 +0900 | [diff] [blame] | 49 | parser.add_argument('--android_manifest', |
| 50 | help='path to the AndroidManifest file. If omitted, a default one is created and used') |
Jaewoong Jung | 036d505 | 2019-06-18 13:10:28 -0700 | [diff] [blame] | 51 | parser.add_argument('--assets_dir', |
| 52 | help='an assets directory to be included in the APEX') |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 53 | parser.add_argument('--file_contexts', |
| 54 | help='selinux file contexts file. Required for "image" APEXs.') |
| 55 | parser.add_argument('--canned_fs_config', |
| 56 | help='canned_fs_config specifies uid/gid/mode of files. Required for ' + |
| 57 | '"image" APEXS.') |
| 58 | parser.add_argument('--key', |
| 59 | help='path to the private key file. Required for "image" APEXs.') |
Jiyong Park | 9c3defa | 2018-12-27 15:09:17 +0900 | [diff] [blame] | 60 | parser.add_argument('--pubkey', |
| 61 | help='path to the public key file. Used to bundle the public key in APEX for testing.') |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 62 | parser.add_argument('input_dir', metavar='INPUT_DIR', |
| 63 | help='the directory having files to be packaged') |
| 64 | parser.add_argument('output', metavar='OUTPUT', |
| 65 | help='name of the APEX file') |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 66 | parser.add_argument('--payload_type', metavar='TYPE', required=False, default="image", |
| 67 | choices=["zip", "image"], |
| 68 | help='type of APEX payload being built "zip" or "image"') |
Jiyong Park | 3e0c65b | 2019-01-05 12:58:11 +0900 | [diff] [blame] | 69 | parser.add_argument('--override_apk_package_name', required=False, |
| 70 | help='package name of the APK container. Default is the apex name in --manifest.') |
Alex Light | 9e6a2e6 | 2019-02-27 14:07:49 -0800 | [diff] [blame] | 71 | parser.add_argument('--android_jar_path', required=False, |
| 72 | default="prebuilts/sdk/current/public/android.jar", |
| 73 | help='path to use as the source of the android API.') |
Alex Light | 18a1b19 | 2019-03-04 14:27:28 -0800 | [diff] [blame] | 74 | apexer_path_in_environ = "APEXER_TOOL_PATH" in os.environ |
| 75 | parser.add_argument('--apexer_tool_path', required=not apexer_path_in_environ, |
| 76 | default=os.environ['APEXER_TOOL_PATH'].split(":") if apexer_path_in_environ else None, |
| 77 | type=lambda s: s.split(":"), |
| 78 | help="""A list of directories containing all the tools used by apexer (e.g. |
| 79 | mke2fs, avbtool, etc.) separated by ':'. Can also be set using the |
| 80 | APEXER_TOOL_PATH environment variable""") |
Jiyong Park | f40a093 | 2019-04-18 17:32:03 +0900 | [diff] [blame] | 81 | parser.add_argument('--target_sdk_version', required=False, |
| 82 | help='Default target SDK version to use for AndroidManifest.xml') |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 83 | return parser.parse_args(argv) |
| 84 | |
Roland Levillain | 3f1c3c9 | 2018-10-02 18:23:58 +0100 | [diff] [blame] | 85 | def FindBinaryPath(binary): |
| 86 | for path in tool_path_list: |
| 87 | binary_path = os.path.join(path, binary) |
| 88 | if os.path.exists(binary_path): |
| 89 | return binary_path |
Alex Light | 18a1b19 | 2019-03-04 14:27:28 -0800 | [diff] [blame] | 90 | raise Exception("Failed to find binary " + binary + " in path " + ":".join(tool_path_list)) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 91 | |
| 92 | def RunCommand(cmd, verbose=False, env=None): |
| 93 | env = env or {} |
| 94 | env.update(os.environ.copy()) |
Jiyong Park | 8537e2a | 2018-09-05 21:34:21 +0900 | [diff] [blame] | 95 | |
Roland Levillain | 3f1c3c9 | 2018-10-02 18:23:58 +0100 | [diff] [blame] | 96 | cmd[0] = FindBinaryPath(cmd[0]) |
Jiyong Park | 8537e2a | 2018-09-05 21:34:21 +0900 | [diff] [blame] | 97 | |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 98 | if verbose: |
| 99 | print("Running: " + " ".join(cmd)) |
| 100 | p = subprocess.Popen( |
| 101 | cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) |
| 102 | output, _ = p.communicate() |
| 103 | |
Andreas Gampe | 8e1ac68 | 2018-11-28 10:44:35 -0800 | [diff] [blame] | 104 | if verbose or p.returncode is not 0: |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 105 | print(output.rstrip()) |
| 106 | |
| 107 | assert p.returncode is 0, "Failed to execute: " + " ".join(cmd) |
| 108 | |
| 109 | return (output, p.returncode) |
| 110 | |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 111 | def GetDirSize(dir_name): |
| 112 | size = 0 |
Jiyong Park | 4edc623 | 2018-09-03 12:31:58 +0900 | [diff] [blame] | 113 | for dirpath, _, filenames in os.walk(dir_name): |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 114 | size += RoundUp(os.path.getsize(dirpath), BLOCK_SIZE) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 115 | for f in filenames: |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 116 | size += RoundUp(os.path.getsize(os.path.join(dirpath, f)), BLOCK_SIZE) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 117 | return size |
| 118 | |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 119 | def GetFilesAndDirsCount(dir_name): |
| 120 | count = 0; |
| 121 | for root, dirs, files in os.walk(dir_name): |
| 122 | count += (len(dirs) + len(files)) |
| 123 | return count |
Jiyong Park | a515307 | 2018-09-04 10:01:41 +0900 | [diff] [blame] | 124 | |
Jiyong Park | d372d3b | 2018-08-28 22:06:56 +0900 | [diff] [blame] | 125 | def RoundUp(size, unit): |
| 126 | assert unit & (unit - 1) == 0 |
| 127 | return (size + unit - 1) & (~(unit - 1)) |
| 128 | |
Jiyong Park | a515307 | 2018-09-04 10:01:41 +0900 | [diff] [blame] | 129 | def PrepareAndroidManifest(package, version): |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 130 | template = """\ |
| 131 | <?xml version="1.0" encoding="utf-8"?> |
| 132 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
Nicholas Lativy | 0e69652 | 2018-11-23 15:16:33 +0000 | [diff] [blame] | 133 | package="{package}" android:versionCode="{version}"> |
Jiyong Park | 5fbeac1 | 2018-11-15 16:05:05 +0900 | [diff] [blame] | 134 | <!-- APEX does not have classes.dex --> |
| 135 | <application android:hasCode="false" /> |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 136 | </manifest> |
| 137 | """ |
Jiyong Park | a515307 | 2018-09-04 10:01:41 +0900 | [diff] [blame] | 138 | return template.format(package=package, version=version) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 139 | |
Jiyong Park | 1c4605a | 2019-02-08 02:49:59 +0900 | [diff] [blame] | 140 | def ValidateAndroidManifest(package, android_manifest): |
| 141 | tree = ET.parse(android_manifest) |
| 142 | manifest_tag = tree.getroot() |
| 143 | package_in_xml = manifest_tag.attrib['package'] |
| 144 | if package_in_xml != package: |
| 145 | raise Exception("Package name '" + package_in_xml + "' in '" + android_manifest + |
| 146 | " differ from package name '" + package + "' in the apex_manifest.json") |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 147 | |
| 148 | def ValidateArgs(args): |
| 149 | if not os.path.exists(args.manifest): |
| 150 | print("Manifest file '" + args.manifest + "' does not exist") |
| 151 | return False |
| 152 | |
| 153 | if not os.path.isfile(args.manifest): |
| 154 | print("Manifest file '" + args.manifest + "' is not a file") |
| 155 | return False |
| 156 | |
Jiyong Park | 1c4605a | 2019-02-08 02:49:59 +0900 | [diff] [blame] | 157 | if args.android_manifest is not None: |
| 158 | if not os.path.exists(args.android_manifest): |
| 159 | print("Android Manifest file '" + args.android_manifest + "' does not exist") |
| 160 | return False |
| 161 | |
| 162 | if not os.path.isfile(args.android_manifest): |
| 163 | print("Android Manifest file '" + args.android_manifest + "' is not a file") |
| 164 | return False |
| 165 | |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 166 | if not os.path.exists(args.input_dir): |
| 167 | print("Input directory '" + args.input_dir + "' does not exist") |
| 168 | return False |
| 169 | |
| 170 | if not os.path.isdir(args.input_dir): |
| 171 | print("Input directory '" + args.input_dir + "' is not a directory") |
| 172 | return False |
| 173 | |
| 174 | if not args.force and os.path.exists(args.output): |
| 175 | print(args.output + ' already exists. Use --force to overwrite.') |
| 176 | return False |
| 177 | |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 178 | if args.payload_type == "image": |
| 179 | if not args.key: |
| 180 | print("Missing --key {keyfile} argument!") |
| 181 | return False |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 182 | |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 183 | if not args.file_contexts: |
| 184 | print("Missing --file_contexts {contexts} argument!") |
| 185 | return False |
| 186 | |
| 187 | if not args.canned_fs_config: |
| 188 | print("Missing --canned_fs_config {config} argument!") |
| 189 | return False |
| 190 | |
| 191 | return True |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 192 | |
| 193 | def CreateApex(args, work_dir): |
| 194 | if not ValidateArgs(args): |
| 195 | return False |
| 196 | |
Jiyong Park | 847d759 | 2018-11-20 10:02:13 +0900 | [diff] [blame] | 197 | if args.verbose: |
| 198 | print "Using tools from " + str(tool_path_list) |
| 199 | |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 200 | try: |
Dario Freni | adbee5d | 2018-12-27 12:44:01 +0000 | [diff] [blame] | 201 | with open(args.manifest, "r") as f: |
| 202 | manifest_raw = f.read() |
| 203 | manifest_apex = ValidateApexManifest(manifest_raw) |
| 204 | except ApexManifestError as err: |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 205 | print("'" + args.manifest + "' is not a valid manifest file") |
Dario Freni | adbee5d | 2018-12-27 12:44:01 +0000 | [diff] [blame] | 206 | print err.errmessage |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 207 | return False |
| 208 | except IOError: |
| 209 | print("Cannot read manifest file: '" + args.manifest + "'") |
| 210 | return False |
| 211 | |
Jiyong Park | 7c10f41 | 2018-08-21 21:28:41 +0900 | [diff] [blame] | 212 | # create an empty ext4 image that is sufficiently big |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 213 | # sufficiently big = size + 16MB margin |
| 214 | size_in_mb = (GetDirSize(args.input_dir) / (1024*1024)) + 16 |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 215 | |
| 216 | content_dir = os.path.join(work_dir, 'content') |
| 217 | os.mkdir(content_dir) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 218 | |
| 219 | # APEX manifest is also included in the image. The manifest is included |
| 220 | # twice: once inside the image and once outside the image (but still |
| 221 | # within the zip container). |
| 222 | manifests_dir = os.path.join(work_dir, 'manifests') |
| 223 | os.mkdir(manifests_dir) |
Dario Freni | 0a21264 | 2018-11-20 18:07:44 +0000 | [diff] [blame] | 224 | manifest_file = os.path.join(manifests_dir, 'apex_manifest.json') |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 225 | if args.verbose: |
| 226 | print('Copying ' + args.manifest + ' to ' + manifest_file) |
| 227 | shutil.copyfile(args.manifest, manifest_file) |
| 228 | |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 229 | if args.payload_type == 'image': |
| 230 | key_name = os.path.basename(os.path.splitext(args.key)[0]) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 231 | |
Abhijeet Kaur | c4b2183 | 2019-01-02 16:50:21 +0000 | [diff] [blame] | 232 | if manifest_apex.name != key_name: |
| 233 | print("package name '" + manifest_apex.name + "' does not match with key name '" + key_name + "'") |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 234 | return False |
| 235 | img_file = os.path.join(content_dir, 'apex_payload.img') |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 236 | |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 237 | # margin is for files that are not under args.input_dir. this consists of |
| 238 | # one inode for apex_manifest.json and 11 reserved inodes for ext4. |
| 239 | # TOBO(b/122991714) eliminate these details. use build_image.py which |
| 240 | # determines the optimal inode count by first building an image and then |
| 241 | # count the inodes actually used. |
| 242 | inode_num_margin = 12 |
| 243 | inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin |
| 244 | |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 245 | cmd = ['mke2fs'] |
| 246 | cmd.extend(['-O', '^has_journal']) # because image is read-only |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 247 | cmd.extend(['-b', str(BLOCK_SIZE)]) |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 248 | cmd.extend(['-m', '0']) # reserved block percentage |
| 249 | cmd.extend(['-t', 'ext4']) |
Jiyong Park | 78e4d3a | 2019-03-29 23:12:12 +0000 | [diff] [blame] | 250 | cmd.extend(['-I', '256']) # inode size |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 251 | cmd.extend(['-N', str(inode_num)]) |
Jiyong Park | 3d44bb4 | 2018-12-18 19:44:54 +0900 | [diff] [blame] | 252 | uu = str(uuid.uuid5(uuid.NAMESPACE_URL, "www.android.com")) |
| 253 | cmd.extend(['-U', uu]) |
| 254 | cmd.extend(['-E', 'hash_seed=' + uu]) |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 255 | cmd.append(img_file) |
| 256 | cmd.append(str(size_in_mb) + 'M') |
Jiyong Park | 3d44bb4 | 2018-12-18 19:44:54 +0900 | [diff] [blame] | 257 | RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"}) |
Jiyong Park | d372d3b | 2018-08-28 22:06:56 +0900 | [diff] [blame] | 258 | |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 259 | # Compile the file context into the binary form |
| 260 | compiled_file_contexts = os.path.join(work_dir, 'file_contexts.bin') |
| 261 | cmd = ['sefcontext_compile'] |
| 262 | cmd.extend(['-o', compiled_file_contexts]) |
| 263 | cmd.append(args.file_contexts) |
| 264 | RunCommand(cmd, args.verbose) |
Jiyong Park | d372d3b | 2018-08-28 22:06:56 +0900 | [diff] [blame] | 265 | |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 266 | # Add files to the image file |
| 267 | cmd = ['e2fsdroid'] |
| 268 | cmd.append('-e') # input is not android_sparse_file |
| 269 | cmd.extend(['-f', args.input_dir]) |
| 270 | cmd.extend(['-T', '0']) # time is set to epoch |
| 271 | cmd.extend(['-S', compiled_file_contexts]) |
| 272 | cmd.extend(['-C', args.canned_fs_config]) |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 273 | cmd.append('-s') # share dup blocks |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 274 | cmd.append(img_file) |
Jiyong Park | 78e4d3a | 2019-03-29 23:12:12 +0000 | [diff] [blame] | 275 | RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"}) |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 276 | |
| 277 | cmd = ['e2fsdroid'] |
| 278 | cmd.append('-e') # input is not android_sparse_file |
| 279 | cmd.extend(['-f', manifests_dir]) |
| 280 | cmd.extend(['-T', '0']) # time is set to epoch |
| 281 | cmd.extend(['-S', compiled_file_contexts]) |
| 282 | cmd.extend(['-C', args.canned_fs_config]) |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 283 | cmd.append('-s') # share dup blocks |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 284 | cmd.append(img_file) |
Jiyong Park | 3d44bb4 | 2018-12-18 19:44:54 +0900 | [diff] [blame] | 285 | RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"}) |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 286 | |
| 287 | # Resize the image file to save space |
| 288 | cmd = ['resize2fs'] |
| 289 | cmd.append('-M') # shrink as small as possible |
| 290 | cmd.append(img_file) |
Jiyong Park | 3d44bb4 | 2018-12-18 19:44:54 +0900 | [diff] [blame] | 291 | RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"}) |
| 292 | |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 293 | |
| 294 | cmd = ['avbtool'] |
| 295 | cmd.append('add_hashtree_footer') |
| 296 | cmd.append('--do_not_generate_fec') |
| 297 | cmd.extend(['--algorithm', 'SHA256_RSA4096']) |
| 298 | cmd.extend(['--key', args.key]) |
| 299 | cmd.extend(['--prop', "apex.key:" + key_name]) |
Jiyong Park | 3d44bb4 | 2018-12-18 19:44:54 +0900 | [diff] [blame] | 300 | # Set up the salt based on manifest content which includes name |
| 301 | # and version |
Dario Freni | adbee5d | 2018-12-27 12:44:01 +0000 | [diff] [blame] | 302 | salt = hashlib.sha256(manifest_raw).hexdigest() |
Jiyong Park | 3d44bb4 | 2018-12-18 19:44:54 +0900 | [diff] [blame] | 303 | cmd.extend(['--salt', salt]) |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 304 | cmd.extend(['--image', img_file]) |
| 305 | RunCommand(cmd, args.verbose) |
| 306 | |
| 307 | # Get the minimum size of the partition required. |
| 308 | # TODO(b/113320014) eliminate this step |
| 309 | info, _ = RunCommand(['avbtool', 'info_image', '--image', img_file], args.verbose) |
| 310 | vbmeta_offset = int(re.search('VBMeta\ offset:\ *([0-9]+)', info).group(1)) |
| 311 | vbmeta_size = int(re.search('VBMeta\ size:\ *([0-9]+)', info).group(1)) |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 312 | partition_size = RoundUp(vbmeta_offset + vbmeta_size, BLOCK_SIZE) + BLOCK_SIZE |
Alex Light | cc62585 | 2018-11-29 17:15:45 -0800 | [diff] [blame] | 313 | |
| 314 | # Resize to the minimum size |
| 315 | # TODO(b/113320014) eliminate this step |
| 316 | cmd = ['avbtool'] |
| 317 | cmd.append('resize_image') |
| 318 | cmd.extend(['--image', img_file]) |
| 319 | cmd.extend(['--partition_size', str(partition_size)]) |
| 320 | RunCommand(cmd, args.verbose) |
| 321 | else: |
| 322 | img_file = os.path.join(content_dir, 'apex_payload.zip') |
| 323 | cmd = ['soong_zip'] |
| 324 | cmd.extend(['-o', img_file]) |
| 325 | cmd.extend(['-C', args.input_dir]) |
| 326 | cmd.extend(['-D', args.input_dir]) |
| 327 | cmd.extend(['-C', manifests_dir]) |
| 328 | cmd.extend(['-D', manifests_dir]) |
| 329 | RunCommand(cmd, args.verbose) |
Jiyong Park | d372d3b | 2018-08-28 22:06:56 +0900 | [diff] [blame] | 330 | |
Jiyong Park | 7c10f41 | 2018-08-21 21:28:41 +0900 | [diff] [blame] | 331 | # package the image file and APEX manifest as an APK. |
Jiyong Park | 1c4605a | 2019-02-08 02:49:59 +0900 | [diff] [blame] | 332 | # The AndroidManifest file is automatically generated if not given. |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 333 | android_manifest_file = os.path.join(work_dir, 'AndroidManifest.xml') |
Jiyong Park | 1c4605a | 2019-02-08 02:49:59 +0900 | [diff] [blame] | 334 | if not args.android_manifest: |
| 335 | if args.verbose: |
| 336 | print('Creating AndroidManifest ' + android_manifest_file) |
| 337 | with open(android_manifest_file, 'w+') as f: |
| 338 | app_package_name = manifest_apex.name |
| 339 | f.write(PrepareAndroidManifest(app_package_name, manifest_apex.version)) |
| 340 | else: |
| 341 | ValidateAndroidManifest(manifest_apex.name, args.android_manifest) |
| 342 | shutil.copyfile(args.android_manifest, android_manifest_file) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 343 | |
| 344 | # copy manifest to the content dir so that it is also accessible |
| 345 | # without mounting the image |
Dario Freni | 0a21264 | 2018-11-20 18:07:44 +0000 | [diff] [blame] | 346 | shutil.copyfile(args.manifest, os.path.join(content_dir, 'apex_manifest.json')) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 347 | |
Jiyong Park | 9c3defa | 2018-12-27 15:09:17 +0900 | [diff] [blame] | 348 | # copy the public key, if specified |
| 349 | if args.pubkey: |
| 350 | shutil.copyfile(args.pubkey, os.path.join(content_dir, "apex_pubkey")) |
| 351 | |
Jiyong Park | bb35247 | 2018-08-25 23:07:07 +0900 | [diff] [blame] | 352 | apk_file = os.path.join(work_dir, 'apex.apk') |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 353 | cmd = ['aapt2'] |
| 354 | cmd.append('link') |
| 355 | cmd.extend(['--manifest', android_manifest_file]) |
Jiyong Park | 1c4605a | 2019-02-08 02:49:59 +0900 | [diff] [blame] | 356 | if args.override_apk_package_name: |
| 357 | cmd.extend(['--rename-manifest-package', args.override_apk_package_name]) |
| 358 | # This version from apex_manifest.json is used when versionCode isn't |
| 359 | # specified in AndroidManifest.xml |
| 360 | cmd.extend(['--version-code', str(manifest_apex.version)]) |
Gavin Corkery | 43bd4b7 | 2019-04-30 10:40:12 +0100 | [diff] [blame] | 361 | if manifest_apex.versionName: |
| 362 | cmd.extend(['--version-name', manifest_apex.versionName]) |
Jiyong Park | f40a093 | 2019-04-18 17:32:03 +0900 | [diff] [blame] | 363 | if args.target_sdk_version: |
| 364 | cmd.extend(['--target-sdk-version', args.target_sdk_version]) |
Jaewoong Jung | 036d505 | 2019-06-18 13:10:28 -0700 | [diff] [blame] | 365 | if args.assets_dir: |
| 366 | cmd.extend(['-A', args.assets_dir]) |
Jiyong Park | 183acd9 | 2019-04-24 12:39:37 +0900 | [diff] [blame] | 367 | # Default value for minSdkVersion. |
Jiyong Park | 5331936 | 2019-05-16 12:43:06 +0900 | [diff] [blame] | 368 | cmd.extend(['--min-sdk-version', '29']) |
Jiyong Park | bb35247 | 2018-08-25 23:07:07 +0900 | [diff] [blame] | 369 | cmd.extend(['-o', apk_file]) |
Alex Light | 9e6a2e6 | 2019-02-27 14:07:49 -0800 | [diff] [blame] | 370 | cmd.extend(['-I', args.android_jar_path]) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 371 | RunCommand(cmd, args.verbose) |
| 372 | |
Jiyong Park | bb35247 | 2018-08-25 23:07:07 +0900 | [diff] [blame] | 373 | zip_file = os.path.join(work_dir, 'apex.zip') |
| 374 | cmd = ['soong_zip'] |
| 375 | cmd.append('-d') # include directories |
| 376 | cmd.extend(['-C', content_dir]) # relative root |
| 377 | cmd.extend(['-D', content_dir]) # input dir |
Martijn Coenen | 09c106c | 2018-09-11 11:00:26 +0200 | [diff] [blame] | 378 | for file_ in os.listdir(content_dir): |
| 379 | if os.path.isfile(os.path.join(content_dir, file_)): |
| 380 | cmd.extend(['-s', file_]) # don't compress any files |
Jiyong Park | bb35247 | 2018-08-25 23:07:07 +0900 | [diff] [blame] | 381 | cmd.extend(['-o', zip_file]) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 382 | RunCommand(cmd, args.verbose) |
Jiyong Park | bb35247 | 2018-08-25 23:07:07 +0900 | [diff] [blame] | 383 | |
| 384 | unaligned_apex_file = os.path.join(work_dir, 'unaligned.apex') |
| 385 | cmd = ['merge_zips'] |
| 386 | cmd.append('-j') # sort |
| 387 | cmd.append(unaligned_apex_file) # output |
| 388 | cmd.append(apk_file) # input |
| 389 | cmd.append(zip_file) # input |
| 390 | RunCommand(cmd, args.verbose) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 391 | |
Jiyong Park | 7c10f41 | 2018-08-21 21:28:41 +0900 | [diff] [blame] | 392 | # Align the files at page boundary for efficient access |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 393 | cmd = ['zipalign'] |
| 394 | cmd.append('-f') |
Jiyong Park | 4cd9b2b | 2019-01-19 13:42:35 +0900 | [diff] [blame] | 395 | cmd.append(str(BLOCK_SIZE)) |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 396 | cmd.append(unaligned_apex_file) |
| 397 | cmd.append(args.output) |
| 398 | RunCommand(cmd, args.verbose) |
| 399 | |
| 400 | if (args.verbose): |
| 401 | print('Created ' + args.output) |
| 402 | |
| 403 | return True |
| 404 | |
| 405 | |
| 406 | class TempDirectory(object): |
| 407 | def __enter__(self): |
| 408 | self.name = tempfile.mkdtemp() |
| 409 | return self.name |
| 410 | |
Jiyong Park | 4edc623 | 2018-09-03 12:31:58 +0900 | [diff] [blame] | 411 | def __exit__(self, *unused): |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 412 | shutil.rmtree(self.name) |
| 413 | |
| 414 | |
| 415 | def main(argv): |
Alex Light | 18a1b19 | 2019-03-04 14:27:28 -0800 | [diff] [blame] | 416 | global tool_path_list |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 417 | args = ParseArgs(argv) |
Alex Light | 18a1b19 | 2019-03-04 14:27:28 -0800 | [diff] [blame] | 418 | tool_path_list = args.apexer_tool_path |
Jiyong Park | da6ab6e | 2018-08-14 23:44:51 +0900 | [diff] [blame] | 419 | with TempDirectory() as work_dir: |
| 420 | success = CreateApex(args, work_dir) |
| 421 | |
| 422 | if not success: |
| 423 | sys.exit(1) |
| 424 | |
| 425 | |
| 426 | if __name__ == '__main__': |
| 427 | main(sys.argv[1:]) |