blob: e57e2691ca950bccf5c5fd7e552ee23b2438ace4 [file] [log] [blame]
Jiyong Parkda6ab6e2018-08-14 23:44:51 +09001#!/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"""
18apexer is a command line tool for creating an APEX file, a package format
19for system components.
20
21Typical usage: apexer input_dir output.apex
22
23"""
24
25import argparse
Jiyong Park3d44bb42018-12-18 19:44:54 +090026import hashlib
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090027import os
Jiyong Parkd372d3b2018-08-28 22:06:56 +090028import re
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090029import shutil
30import subprocess
31import sys
32import tempfile
Jiyong Park3d44bb42018-12-18 19:44:54 +090033import uuid
Jiyong Park1c4605a2019-02-08 02:49:59 +090034import xml.etree.ElementTree as ET
Dario Freniadbee5d2018-12-27 12:44:01 +000035from apex_manifest import ValidateApexManifest
36from apex_manifest import ApexManifestError
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090037
Alex Light18a1b192019-03-04 14:27:28 -080038tool_path_list = None
Jiyong Park4cd9b2b2019-01-19 13:42:35 +090039BLOCK_SIZE = 4096
40
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090041def 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 Freni0a212642018-11-20 18:07:44 +000047 parser.add_argument('--manifest', default='apex_manifest.json',
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090048 help='path to the APEX manifest file')
Jiyong Park1c4605a2019-02-08 02:49:59 +090049 parser.add_argument('--android_manifest',
50 help='path to the AndroidManifest file. If omitted, a default one is created and used')
Jaewoong Jung036d5052019-06-18 13:10:28 -070051 parser.add_argument('--assets_dir',
52 help='an assets directory to be included in the APEX')
Alex Lightcc625852018-11-29 17:15:45 -080053 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 Park9c3defa2018-12-27 15:09:17 +090060 parser.add_argument('--pubkey',
61 help='path to the public key file. Used to bundle the public key in APEX for testing.')
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090062 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 Lightcc625852018-11-29 17:15:45 -080066 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 Park3e0c65b2019-01-05 12:58:11 +090069 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 Light9e6a2e62019-02-27 14:07:49 -080071 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 Light18a1b192019-03-04 14:27:28 -080074 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 Parkf40a0932019-04-18 17:32:03 +090081 parser.add_argument('--target_sdk_version', required=False,
82 help='Default target SDK version to use for AndroidManifest.xml')
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090083 return parser.parse_args(argv)
84
Roland Levillain3f1c3c92018-10-02 18:23:58 +010085def 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 Light18a1b192019-03-04 14:27:28 -080090 raise Exception("Failed to find binary " + binary + " in path " + ":".join(tool_path_list))
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090091
92def RunCommand(cmd, verbose=False, env=None):
93 env = env or {}
94 env.update(os.environ.copy())
Jiyong Park8537e2a2018-09-05 21:34:21 +090095
Roland Levillain3f1c3c92018-10-02 18:23:58 +010096 cmd[0] = FindBinaryPath(cmd[0])
Jiyong Park8537e2a2018-09-05 21:34:21 +090097
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090098 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 Gampe8e1ac682018-11-28 10:44:35 -0800104 if verbose or p.returncode is not 0:
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900105 print(output.rstrip())
106
107 assert p.returncode is 0, "Failed to execute: " + " ".join(cmd)
108
109 return (output, p.returncode)
110
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900111def GetDirSize(dir_name):
112 size = 0
Jiyong Park4edc6232018-09-03 12:31:58 +0900113 for dirpath, _, filenames in os.walk(dir_name):
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900114 size += RoundUp(os.path.getsize(dirpath), BLOCK_SIZE)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900115 for f in filenames:
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900116 size += RoundUp(os.path.getsize(os.path.join(dirpath, f)), BLOCK_SIZE)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900117 return size
118
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900119def 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 Parka5153072018-09-04 10:01:41 +0900124
Jiyong Parkd372d3b2018-08-28 22:06:56 +0900125def RoundUp(size, unit):
126 assert unit & (unit - 1) == 0
127 return (size + unit - 1) & (~(unit - 1))
128
Jiyong Parka5153072018-09-04 10:01:41 +0900129def PrepareAndroidManifest(package, version):
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900130 template = """\
131<?xml version="1.0" encoding="utf-8"?>
132<manifest xmlns:android="http://schemas.android.com/apk/res/android"
Nicholas Lativy0e696522018-11-23 15:16:33 +0000133 package="{package}" android:versionCode="{version}">
Jiyong Park5fbeac12018-11-15 16:05:05 +0900134 <!-- APEX does not have classes.dex -->
135 <application android:hasCode="false" />
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900136</manifest>
137"""
Jiyong Parka5153072018-09-04 10:01:41 +0900138 return template.format(package=package, version=version)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900139
Jiyong Park1c4605a2019-02-08 02:49:59 +0900140def 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 Parkda6ab6e2018-08-14 23:44:51 +0900147
148def 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 Park1c4605a2019-02-08 02:49:59 +0900157 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 Parkda6ab6e2018-08-14 23:44:51 +0900166 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 Lightcc625852018-11-29 17:15:45 -0800178 if args.payload_type == "image":
179 if not args.key:
180 print("Missing --key {keyfile} argument!")
181 return False
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900182
Alex Lightcc625852018-11-29 17:15:45 -0800183 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 Parkda6ab6e2018-08-14 23:44:51 +0900192
193def CreateApex(args, work_dir):
194 if not ValidateArgs(args):
195 return False
196
Jiyong Park847d7592018-11-20 10:02:13 +0900197 if args.verbose:
198 print "Using tools from " + str(tool_path_list)
199
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900200 try:
Dario Freniadbee5d2018-12-27 12:44:01 +0000201 with open(args.manifest, "r") as f:
202 manifest_raw = f.read()
203 manifest_apex = ValidateApexManifest(manifest_raw)
204 except ApexManifestError as err:
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900205 print("'" + args.manifest + "' is not a valid manifest file")
Dario Freniadbee5d2018-12-27 12:44:01 +0000206 print err.errmessage
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900207 return False
208 except IOError:
209 print("Cannot read manifest file: '" + args.manifest + "'")
210 return False
211
Jiyong Park7c10f412018-08-21 21:28:41 +0900212 # create an empty ext4 image that is sufficiently big
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900213 # sufficiently big = size + 16MB margin
214 size_in_mb = (GetDirSize(args.input_dir) / (1024*1024)) + 16
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900215
216 content_dir = os.path.join(work_dir, 'content')
217 os.mkdir(content_dir)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900218
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 Freni0a212642018-11-20 18:07:44 +0000224 manifest_file = os.path.join(manifests_dir, 'apex_manifest.json')
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900225 if args.verbose:
226 print('Copying ' + args.manifest + ' to ' + manifest_file)
227 shutil.copyfile(args.manifest, manifest_file)
228
Alex Lightcc625852018-11-29 17:15:45 -0800229 if args.payload_type == 'image':
230 key_name = os.path.basename(os.path.splitext(args.key)[0])
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900231
Abhijeet Kaurc4b21832019-01-02 16:50:21 +0000232 if manifest_apex.name != key_name:
233 print("package name '" + manifest_apex.name + "' does not match with key name '" + key_name + "'")
Alex Lightcc625852018-11-29 17:15:45 -0800234 return False
235 img_file = os.path.join(content_dir, 'apex_payload.img')
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900236
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900237 # 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 Lightcc625852018-11-29 17:15:45 -0800245 cmd = ['mke2fs']
246 cmd.extend(['-O', '^has_journal']) # because image is read-only
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900247 cmd.extend(['-b', str(BLOCK_SIZE)])
Alex Lightcc625852018-11-29 17:15:45 -0800248 cmd.extend(['-m', '0']) # reserved block percentage
249 cmd.extend(['-t', 'ext4'])
Jiyong Park78e4d3a2019-03-29 23:12:12 +0000250 cmd.extend(['-I', '256']) # inode size
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900251 cmd.extend(['-N', str(inode_num)])
Jiyong Park3d44bb42018-12-18 19:44:54 +0900252 uu = str(uuid.uuid5(uuid.NAMESPACE_URL, "www.android.com"))
253 cmd.extend(['-U', uu])
254 cmd.extend(['-E', 'hash_seed=' + uu])
Alex Lightcc625852018-11-29 17:15:45 -0800255 cmd.append(img_file)
256 cmd.append(str(size_in_mb) + 'M')
Jiyong Park3d44bb42018-12-18 19:44:54 +0900257 RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"})
Jiyong Parkd372d3b2018-08-28 22:06:56 +0900258
Alex Lightcc625852018-11-29 17:15:45 -0800259 # 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 Parkd372d3b2018-08-28 22:06:56 +0900265
Alex Lightcc625852018-11-29 17:15:45 -0800266 # 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 Park4cd9b2b2019-01-19 13:42:35 +0900273 cmd.append('-s') # share dup blocks
Alex Lightcc625852018-11-29 17:15:45 -0800274 cmd.append(img_file)
Jiyong Park78e4d3a2019-03-29 23:12:12 +0000275 RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"})
Alex Lightcc625852018-11-29 17:15:45 -0800276
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 Park4cd9b2b2019-01-19 13:42:35 +0900283 cmd.append('-s') # share dup blocks
Alex Lightcc625852018-11-29 17:15:45 -0800284 cmd.append(img_file)
Jiyong Park3d44bb42018-12-18 19:44:54 +0900285 RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"})
Alex Lightcc625852018-11-29 17:15:45 -0800286
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 Park3d44bb42018-12-18 19:44:54 +0900291 RunCommand(cmd, args.verbose, {"E2FSPROGS_FAKE_TIME": "1"})
292
Alex Lightcc625852018-11-29 17:15:45 -0800293
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 Park3d44bb42018-12-18 19:44:54 +0900300 # Set up the salt based on manifest content which includes name
301 # and version
Dario Freniadbee5d2018-12-27 12:44:01 +0000302 salt = hashlib.sha256(manifest_raw).hexdigest()
Jiyong Park3d44bb42018-12-18 19:44:54 +0900303 cmd.extend(['--salt', salt])
Alex Lightcc625852018-11-29 17:15:45 -0800304 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 Park4cd9b2b2019-01-19 13:42:35 +0900312 partition_size = RoundUp(vbmeta_offset + vbmeta_size, BLOCK_SIZE) + BLOCK_SIZE
Alex Lightcc625852018-11-29 17:15:45 -0800313
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 Parkd372d3b2018-08-28 22:06:56 +0900330
Jiyong Park7c10f412018-08-21 21:28:41 +0900331 # package the image file and APEX manifest as an APK.
Jiyong Park1c4605a2019-02-08 02:49:59 +0900332 # The AndroidManifest file is automatically generated if not given.
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900333 android_manifest_file = os.path.join(work_dir, 'AndroidManifest.xml')
Jiyong Park1c4605a2019-02-08 02:49:59 +0900334 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 Parkda6ab6e2018-08-14 23:44:51 +0900343
344 # copy manifest to the content dir so that it is also accessible
345 # without mounting the image
Dario Freni0a212642018-11-20 18:07:44 +0000346 shutil.copyfile(args.manifest, os.path.join(content_dir, 'apex_manifest.json'))
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900347
Jiyong Park9c3defa2018-12-27 15:09:17 +0900348 # copy the public key, if specified
349 if args.pubkey:
350 shutil.copyfile(args.pubkey, os.path.join(content_dir, "apex_pubkey"))
351
Jiyong Parkbb352472018-08-25 23:07:07 +0900352 apk_file = os.path.join(work_dir, 'apex.apk')
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900353 cmd = ['aapt2']
354 cmd.append('link')
355 cmd.extend(['--manifest', android_manifest_file])
Jiyong Park1c4605a2019-02-08 02:49:59 +0900356 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 Corkery43bd4b72019-04-30 10:40:12 +0100361 if manifest_apex.versionName:
362 cmd.extend(['--version-name', manifest_apex.versionName])
Jiyong Parkf40a0932019-04-18 17:32:03 +0900363 if args.target_sdk_version:
364 cmd.extend(['--target-sdk-version', args.target_sdk_version])
Jaewoong Jung036d5052019-06-18 13:10:28 -0700365 if args.assets_dir:
366 cmd.extend(['-A', args.assets_dir])
Jiyong Park183acd92019-04-24 12:39:37 +0900367 # Default value for minSdkVersion.
Jiyong Park53319362019-05-16 12:43:06 +0900368 cmd.extend(['--min-sdk-version', '29'])
Jiyong Parkbb352472018-08-25 23:07:07 +0900369 cmd.extend(['-o', apk_file])
Alex Light9e6a2e62019-02-27 14:07:49 -0800370 cmd.extend(['-I', args.android_jar_path])
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900371 RunCommand(cmd, args.verbose)
372
Jiyong Parkbb352472018-08-25 23:07:07 +0900373 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 Coenen09c106c2018-09-11 11:00:26 +0200378 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 Parkbb352472018-08-25 23:07:07 +0900381 cmd.extend(['-o', zip_file])
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900382 RunCommand(cmd, args.verbose)
Jiyong Parkbb352472018-08-25 23:07:07 +0900383
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 Parkda6ab6e2018-08-14 23:44:51 +0900391
Jiyong Park7c10f412018-08-21 21:28:41 +0900392 # Align the files at page boundary for efficient access
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900393 cmd = ['zipalign']
394 cmd.append('-f')
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900395 cmd.append(str(BLOCK_SIZE))
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900396 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
406class TempDirectory(object):
407 def __enter__(self):
408 self.name = tempfile.mkdtemp()
409 return self.name
410
Jiyong Park4edc6232018-09-03 12:31:58 +0900411 def __exit__(self, *unused):
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900412 shutil.rmtree(self.name)
413
414
415def main(argv):
Alex Light18a1b192019-03-04 14:27:28 -0800416 global tool_path_list
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900417 args = ParseArgs(argv)
Alex Light18a1b192019-03-04 14:27:28 -0800418 tool_path_list = args.apexer_tool_path
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900419 with TempDirectory() as work_dir:
420 success = CreateApex(args, work_dir)
421
422 if not success:
423 sys.exit(1)
424
425
426if __name__ == '__main__':
427 main(sys.argv[1:])