blob: f698c8c81298ea328e97628195bc96727706350a [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.
Jiyong Park34c821c2019-06-03 21:36:53 +090016"""apexer is a command line tool for creating an APEX file, a package format for system components.
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090017
18Typical usage: apexer input_dir output.apex
19
20"""
21
Dario Frenied536ee2020-01-02 18:52:59 +000022import apex_build_info_pb2
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090023import argparse
Jiyong Park3d44bb42018-12-18 19:44:54 +090024import hashlib
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090025import os
Jiyong Parkd372d3b2018-08-28 22:06:56 +090026import re
Tianjie Xu65bf3312020-01-30 17:27:03 -080027import shlex
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090028import shutil
29import subprocess
30import sys
31import tempfile
Jiyong Park3d44bb42018-12-18 19:44:54 +090032import uuid
Jiyong Park1c4605a2019-02-08 02:49:59 +090033import xml.etree.ElementTree as ET
Dario Freniadbee5d2018-12-27 12:44:01 +000034from apex_manifest import ValidateApexManifest
35from apex_manifest import ApexManifestError
Baligh Uddin68f16e52020-02-19 21:39:33 -080036from 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
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090043
Alex Light18a1b192019-03-04 14:27:28 -080044tool_path_list = None
Jiyong Park4cd9b2b2019-01-19 13:42:35 +090045BLOCK_SIZE = 4096
46
Jiyong Park34c821c2019-06-03 21:36:53 +090047
Jiyong Parkda6ab6e2018-08-14 23:44:51 +090048def ParseArgs(argv):
49 parser = argparse.ArgumentParser(description='Create an APEX file')
Jiyong Park34c821c2019-06-03 21:36:53 +090050 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',
Jooyung Hancd4d81e2019-11-02 02:47:22 +090056 default='apex_manifest.pb',
57 help='path to the APEX manifest file (.pb)')
58 parser.add_argument(
59 '--manifest_json',
Jooyung Han2426fc92019-11-12 13:03:50 +090060 required=False,
Jooyung Hancd4d81e2019-11-02 02:47:22 +090061 help='path to the APEX manifest file (Q compatible .json)')
62 parser.add_argument(
Jiyong Park34c821c2019-06-03 21:36:53 +090063 '--android_manifest',
64 help='path to the AndroidManifest file. If omitted, a default one is created and used'
65 )
66 parser.add_argument(
Baligh Uddin68f16e52020-02-19 21:39:33 -080067 '--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(
Jaewoong Jung2247d1d2019-06-18 13:10:28 -070071 '--assets_dir',
72 help='an assets directory to be included in the APEX'
73 )
74 parser.add_argument(
Jiyong Park34c821c2019-06-03 21:36:53 +090075 '--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(
Tianjie Xu65bf3312020-01-30 17:27:03 -080088 '--signing_args',
89 help='the extra signing arguments passed to avbtool. Used for "image" APEXs.'
90 )
91 parser.add_argument(
Jiyong Park34c821c2019-06-03 21:36:53 +090092 '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(
Jooyung Han78976852019-06-15 03:36:34 +0900109 '--no_hashtree',
110 required=False,
111 action='store_true',
112 help='hashtree is omitted from "image".'
113 )
114 parser.add_argument(
Jiyong Park34c821c2019-06-03 21:36:53 +0900115 '--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.
Alex Light18a1b192019-03-04 14:27:28 -0800127 mke2fs, avbtool, etc.) separated by ':'. Can also be set using the
128 APEXER_TOOL_PATH environment variable""")
Jiyong Park34c821c2019-06-03 21:36:53 +0900129 parser.add_argument(
130 '--target_sdk_version',
131 required=False,
132 help='Default target SDK version to use for AndroidManifest.xml')
Jooyung Han500eba62019-09-27 00:01:03 +0900133 parser.add_argument(
Baligh Uddin38b77da2020-01-18 20:28:11 -0800134 '--min_sdk_version',
135 required=False,
136 help='Default Min SDK version to use for AndroidManifest.xml')
137 parser.add_argument(
Jooyung Han500eba62019-09-27 00:01:03 +0900138 '--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.')
Dario Frenied536ee2020-01-02 18:52:59 +0000142 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(
Dario Freni29f6e452020-01-07 15:48:04 +0000148 '--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(
Dario Frenied536ee2020-01-02 18:52:59 +0000154 '--build_info',
155 required=False,
156 help='Build information file to be used for default values.')
Mohammad Samiul Islam6b4bce22020-02-26 11:37:29 +0000157 parser.add_argument(
158 '--payload_only',
159 action='store_true',
160 help='Outputs the payload image/zip only.'
161 )
Mohammad Samiul Islam2cc12eb2020-02-27 17:26:59 +0000162 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 )
Dario Frenibb779fc2020-04-27 18:26:47 +0100168 parser.add_argument(
169 '--unsigned_payload',
170 action='store_true',
171 help="""Skip signing the apex payload. Used only for testing purposes."""
172 )
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900173 return parser.parse_args(argv)
174
Jiyong Park34c821c2019-06-03 21:36:53 +0900175
Roland Levillain3f1c3c92018-10-02 18:23:58 +0100176def 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
Jiyong Park34c821c2019-06-03 21:36:53 +0900181 raise Exception('Failed to find binary ' + binary + ' in path ' +
182 ':'.join(tool_path_list))
183
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900184
185def RunCommand(cmd, verbose=False, env=None):
186 env = env or {}
187 env.update(os.environ.copy())
Jiyong Park8537e2a2018-09-05 21:34:21 +0900188
Roland Levillain3f1c3c92018-10-02 18:23:58 +0100189 cmd[0] = FindBinaryPath(cmd[0])
Jiyong Park8537e2a2018-09-05 21:34:21 +0900190
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900191 if verbose:
Jiyong Park34c821c2019-06-03 21:36:53 +0900192 print('Running: ' + ' '.join(cmd))
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900193 p = subprocess.Popen(
194 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
195 output, _ = p.communicate()
196
Andreas Gampe8e1ac682018-11-28 10:44:35 -0800197 if verbose or p.returncode is not 0:
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900198 print(output.rstrip())
199
Jiyong Park34c821c2019-06-03 21:36:53 +0900200 assert p.returncode is 0, 'Failed to execute: ' + ' '.join(cmd)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900201
202 return (output, p.returncode)
203
Jiyong Park34c821c2019-06-03 21:36:53 +0900204
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900205def GetDirSize(dir_name):
206 size = 0
Jiyong Park4edc6232018-09-03 12:31:58 +0900207 for dirpath, _, filenames in os.walk(dir_name):
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900208 size += RoundUp(os.path.getsize(dirpath), BLOCK_SIZE)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900209 for f in filenames:
Jiyong Park3c126752019-11-22 17:44:31 +0900210 path = os.path.join(dirpath, f)
211 if not os.path.isfile(path):
212 continue
213 size += RoundUp(os.path.getsize(path), BLOCK_SIZE)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900214 return size
215
Jiyong Park34c821c2019-06-03 21:36:53 +0900216
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900217def GetFilesAndDirsCount(dir_name):
Jiyong Park34c821c2019-06-03 21:36:53 +0900218 count = 0
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900219 for root, dirs, files in os.walk(dir_name):
220 count += (len(dirs) + len(files))
221 return count
Jiyong Parka5153072018-09-04 10:01:41 +0900222
Jiyong Park34c821c2019-06-03 21:36:53 +0900223
Jiyong Parkd372d3b2018-08-28 22:06:56 +0900224def RoundUp(size, unit):
225 assert unit & (unit - 1) == 0
226 return (size + unit - 1) & (~(unit - 1))
227
Jiyong Park34c821c2019-06-03 21:36:53 +0900228
Jiyong Parka5153072018-09-04 10:01:41 +0900229def PrepareAndroidManifest(package, version):
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900230 template = """\
231<?xml version="1.0" encoding="utf-8"?>
232<manifest xmlns:android="http://schemas.android.com/apk/res/android"
Nicholas Lativy0e696522018-11-23 15:16:33 +0000233 package="{package}" android:versionCode="{version}">
Jiyong Park5fbeac12018-11-15 16:05:05 +0900234 <!-- APEX does not have classes.dex -->
235 <application android:hasCode="false" />
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900236</manifest>
237"""
Jiyong Parka5153072018-09-04 10:01:41 +0900238 return template.format(package=package, version=version)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900239
Jiyong Park34c821c2019-06-03 21:36:53 +0900240
Jiyong Park1c4605a2019-02-08 02:49:59 +0900241def 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:
Jiyong Park34c821c2019-06-03 21:36:53 +0900246 raise Exception("Package name '" + package_in_xml + "' in '" +
247 android_manifest + " differ from package name '" + package +
Jooyung Han2426fc92019-11-12 13:03:50 +0900248 "' in the apex_manifest.pb")
Jiyong Park34c821c2019-06-03 21:36:53 +0900249
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900250
251def ValidateArgs(args):
Dario Frenied536ee2020-01-02 18:52:59 +0000252 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
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900262 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
Jiyong Park1c4605a2019-02-08 02:49:59 +0900270 if args.android_manifest is not None:
271 if not os.path.exists(args.android_manifest):
Jiyong Park34c821c2019-06-03 21:36:53 +0900272 print("Android Manifest file '" + args.android_manifest +
273 "' does not exist")
Jiyong Park1c4605a2019-02-08 02:49:59 +0900274 return False
275
276 if not os.path.isfile(args.android_manifest):
Jiyong Park34c821c2019-06-03 21:36:53 +0900277 print("Android Manifest file '" + args.android_manifest +
278 "' is not a file")
Jiyong Park1c4605a2019-02-08 02:49:59 +0900279 return False
Dario Frenied536ee2020-01-02 18:52:59 +0000280 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
Jiyong Park1c4605a2019-02-08 02:49:59 +0900284
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900285 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
Mohammad Samiul Islam2cc12eb2020-02-27 17:26:59 +0000297 if args.unsigned_payload_only:
298 args.payload_only = True;
Dario Frenibb779fc2020-04-27 18:26:47 +0100299 args.unsigned_payload = True;
Mohammad Samiul Islam2cc12eb2020-02-27 17:26:59 +0000300
Jiyong Park34c821c2019-06-03 21:36:53 +0900301 if args.payload_type == 'image':
Dario Frenibb779fc2020-04-27 18:26:47 +0100302 if not args.key and not args.unsigned_payload:
Jiyong Park34c821c2019-06-03 21:36:53 +0900303 print('Missing --key {keyfile} argument!')
Alex Lightcc625852018-11-29 17:15:45 -0800304 return False
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900305
Alex Lightcc625852018-11-29 17:15:45 -0800306 if not args.file_contexts:
Dario Frenied536ee2020-01-02 18:52:59 +0000307 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
Alex Lightcc625852018-11-29 17:15:45 -0800314
315 if not args.canned_fs_config:
Dario Frenied536ee2020-01-02 18:52:59 +0000316 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
Alex Lightcc625852018-11-29 17:15:45 -0800324
Dario Freni3601ea42020-01-23 10:03:25 +0000325 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
Nikita Ioffe81df65e2020-03-23 22:04:50 +0000340 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
Alex Lightcc625852018-11-29 17:15:45 -0800350 return True
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900351
Dario Frenied536ee2020-01-02 18:52:59 +0000352def GenerateBuildInfo(args):
353 build_info = apex_build_info_pb2.ApexBuildInfo()
Dario Freni29f6e452020-01-07 15:48:04 +0000354 if (args.include_cmd_line_in_build_info):
355 build_info.apexer_command_line = str(sys.argv)
Dario Frenied536ee2020-01-02 18:52:59 +0000356
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
Dario Freni3601ea42020-01-23 10:03:25 +0000366 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
Nikita Ioffe81df65e2020-03-23 22:04:50 +0000375 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
Dario Frenied536ee2020-01-02 18:52:59 +0000381 return build_info
Jiyong Park34c821c2019-06-03 21:36:53 +0900382
Baligh Uddin68f16e52020-02-19 21:39:33 -0800383def 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
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900431def CreateApex(args, work_dir):
432 if not ValidateArgs(args):
433 return False
434
Jiyong Park847d7592018-11-20 10:02:13 +0900435 if args.verbose:
Jiyong Park34c821c2019-06-03 21:36:53 +0900436 print 'Using tools from ' + str(tool_path_list)
Jiyong Park847d7592018-11-20 10:02:13 +0900437
Jooyung Hancd4d81e2019-11-02 02:47:22 +0900438 def copyfile(src, dst):
439 if args.verbose:
440 print('Copying ' + src + ' to ' + dst)
441 shutil.copyfile(src, dst)
442
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900443 try:
Jooyung Hancd4d81e2019-11-02 02:47:22 +0900444 manifest_apex = ValidateApexManifest(args.manifest)
Dario Freniadbee5d2018-12-27 12:44:01 +0000445 except ApexManifestError as err:
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900446 print("'" + args.manifest + "' is not a valid manifest file")
Dario Freniadbee5d2018-12-27 12:44:01 +0000447 print err.errmessage
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900448 return False
449 except IOError:
450 print("Cannot read manifest file: '" + args.manifest + "'")
451 return False
452
Jiyong Park7c10f412018-08-21 21:28:41 +0900453 # create an empty ext4 image that is sufficiently big
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900454 # sufficiently big = size + 16MB margin
Jiyong Park34c821c2019-06-03 21:36:53 +0900455 size_in_mb = (GetDirSize(args.input_dir) / (1024 * 1024)) + 16
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900456
457 content_dir = os.path.join(work_dir, 'content')
458 os.mkdir(content_dir)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900459
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)
Jooyung Hancd4d81e2019-11-02 02:47:22 +0900465 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'))
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900469
Alex Lightcc625852018-11-29 17:15:45 -0800470 if args.payload_type == 'image':
Dario Frenibb779fc2020-04-27 18:26:47 +0100471 if args.do_not_check_keyname or args.unsigned_payload:
Jooyung Han500eba62019-09-27 00:01:03 +0900472 key_name = manifest_apex.name
Mohammad Samiul Islam2cc12eb2020-02-27 17:26:59 +0000473 else:
474 key_name = os.path.basename(os.path.splitext(args.key)[0])
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900475
Abhijeet Kaurc4b21832019-01-02 16:50:21 +0000476 if manifest_apex.name != key_name:
Jiyong Park34c821c2019-06-03 21:36:53 +0900477 print("package name '" + manifest_apex.name +
478 "' does not match with key name '" + key_name + "'")
Alex Lightcc625852018-11-29 17:15:45 -0800479 return False
480 img_file = os.path.join(content_dir, 'apex_payload.img')
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900481
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900482 # margin is for files that are not under args.input_dir. this consists of
Jooyung Hancd4d81e2019-11-02 02:47:22 +0900483 # n inodes for apex_manifest files and 11 reserved inodes for ext4.
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900484 # 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.
Jooyung Hancd4d81e2019-11-02 02:47:22 +0900487 inode_num_margin = GetFilesAndDirsCount(manifests_dir) + 11
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900488 inode_num = GetFilesAndDirsCount(args.input_dir) + inode_num_margin
489
Alex Lightcc625852018-11-29 17:15:45 -0800490 cmd = ['mke2fs']
Jiyong Park34c821c2019-06-03 21:36:53 +0900491 cmd.extend(['-O', '^has_journal']) # because image is read-only
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900492 cmd.extend(['-b', str(BLOCK_SIZE)])
Jiyong Park34c821c2019-06-03 21:36:53 +0900493 cmd.extend(['-m', '0']) # reserved block percentage
Alex Lightcc625852018-11-29 17:15:45 -0800494 cmd.extend(['-t', 'ext4'])
Jiyong Park34c821c2019-06-03 21:36:53 +0900495 cmd.extend(['-I', '256']) # inode size
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900496 cmd.extend(['-N', str(inode_num)])
Jiyong Park34c821c2019-06-03 21:36:53 +0900497 uu = str(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com'))
Jiyong Park3d44bb42018-12-18 19:44:54 +0900498 cmd.extend(['-U', uu])
499 cmd.extend(['-E', 'hash_seed=' + uu])
Alex Lightcc625852018-11-29 17:15:45 -0800500 cmd.append(img_file)
501 cmd.append(str(size_in_mb) + 'M')
Jiyong Park34c821c2019-06-03 21:36:53 +0900502 RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
Jiyong Parkd372d3b2018-08-28 22:06:56 +0900503
Alex Lightcc625852018-11-29 17:15:45 -0800504 # 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)
Jiyong Parkd372d3b2018-08-28 22:06:56 +0900510
Alex Lightcc625852018-11-29 17:15:45 -0800511 # Add files to the image file
512 cmd = ['e2fsdroid']
Jiyong Park34c821c2019-06-03 21:36:53 +0900513 cmd.append('-e') # input is not android_sparse_file
Alex Lightcc625852018-11-29 17:15:45 -0800514 cmd.extend(['-f', args.input_dir])
Jiyong Park34c821c2019-06-03 21:36:53 +0900515 cmd.extend(['-T', '0']) # time is set to epoch
Alex Lightcc625852018-11-29 17:15:45 -0800516 cmd.extend(['-S', compiled_file_contexts])
517 cmd.extend(['-C', args.canned_fs_config])
Jiyong Park34c821c2019-06-03 21:36:53 +0900518 cmd.append('-s') # share dup blocks
Alex Lightcc625852018-11-29 17:15:45 -0800519 cmd.append(img_file)
Jiyong Park34c821c2019-06-03 21:36:53 +0900520 RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
Alex Lightcc625852018-11-29 17:15:45 -0800521
522 cmd = ['e2fsdroid']
Jiyong Park34c821c2019-06-03 21:36:53 +0900523 cmd.append('-e') # input is not android_sparse_file
Alex Lightcc625852018-11-29 17:15:45 -0800524 cmd.extend(['-f', manifests_dir])
Jiyong Park34c821c2019-06-03 21:36:53 +0900525 cmd.extend(['-T', '0']) # time is set to epoch
Alex Lightcc625852018-11-29 17:15:45 -0800526 cmd.extend(['-S', compiled_file_contexts])
527 cmd.extend(['-C', args.canned_fs_config])
Jiyong Park34c821c2019-06-03 21:36:53 +0900528 cmd.append('-s') # share dup blocks
Alex Lightcc625852018-11-29 17:15:45 -0800529 cmd.append(img_file)
Jiyong Park34c821c2019-06-03 21:36:53 +0900530 RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
Alex Lightcc625852018-11-29 17:15:45 -0800531
532 # Resize the image file to save space
533 cmd = ['resize2fs']
Jiyong Park34c821c2019-06-03 21:36:53 +0900534 cmd.append('-M') # shrink as small as possible
Alex Lightcc625852018-11-29 17:15:45 -0800535 cmd.append(img_file)
Jiyong Park34c821c2019-06-03 21:36:53 +0900536 RunCommand(cmd, args.verbose, {'E2FSPROGS_FAKE_TIME': '1'})
Alex Lightcc625852018-11-29 17:15:45 -0800537
Mohammad Samiul Islam2cc12eb2020-02-27 17:26:59 +0000538 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
Dario Frenibb779fc2020-04-27 18:26:47 +0100544 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'])
Jiyong Park2934d552020-05-09 15:09:43 +0900549 cmd.extend(['--hash_algorithm', 'sha256'])
Dario Frenibb779fc2020-04-27 18:26:47 +0100550 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)
Alex Lightcc625852018-11-29 17:15:45 -0800562
Dario Frenibb779fc2020-04-27 18:26:47 +0100563 # 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
Alex Lightcc625852018-11-29 17:15:45 -0800571
Dario Frenibb779fc2020-04-27 18:26:47 +0100572 # 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)
Alex Lightcc625852018-11-29 17:15:45 -0800579 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)
Jiyong Parkd372d3b2018-08-28 22:06:56 +0900588
Mohammad Samiul Islam6b4bce22020-02-26 11:37:29 +0000589 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
Jiyong Park7c10f412018-08-21 21:28:41 +0900595 # package the image file and APEX manifest as an APK.
Jiyong Park1c4605a2019-02-08 02:49:59 +0900596 # The AndroidManifest file is automatically generated if not given.
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900597 android_manifest_file = os.path.join(work_dir, 'AndroidManifest.xml')
Jiyong Park1c4605a2019-02-08 02:49:59 +0900598 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))
Dario Frenied536ee2020-01-02 18:52:59 +0000604 args.android_manifest = android_manifest_file
Jiyong Park1c4605a2019-02-08 02:49:59 +0900605 else:
606 ValidateAndroidManifest(manifest_apex.name, args.android_manifest)
607 shutil.copyfile(args.android_manifest, android_manifest_file)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900608
Baligh Uddin68f16e52020-02-19 21:39:33 -0800609 # 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
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900614 # copy manifest to the content dir so that it is also accessible
615 # without mounting the image
Jooyung Hancd4d81e2019-11-02 02:47:22 +0900616 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'))
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900619
Jiyong Park9c3defa2018-12-27 15:09:17 +0900620 # copy the public key, if specified
621 if args.pubkey:
Jiyong Park34c821c2019-06-03 21:36:53 +0900622 shutil.copyfile(args.pubkey, os.path.join(content_dir, 'apex_pubkey'))
Jiyong Park9c3defa2018-12-27 15:09:17 +0900623
Dario Frenied536ee2020-01-02 18:52:59 +0000624 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
Jiyong Parkbb352472018-08-25 23:07:07 +0900629 apk_file = os.path.join(work_dir, 'apex.apk')
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900630 cmd = ['aapt2']
631 cmd.append('link')
632 cmd.extend(['--manifest', android_manifest_file])
Jiyong Park1c4605a2019-02-08 02:49:59 +0900633 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)])
Gavin Corkery94638562019-04-30 10:40:12 +0100638 if manifest_apex.versionName:
639 cmd.extend(['--version-name', manifest_apex.versionName])
Jiyong Parkea0208f2019-04-18 17:32:03 +0900640 if args.target_sdk_version:
641 cmd.extend(['--target-sdk-version', args.target_sdk_version])
Baligh Uddin38b77da2020-01-18 20:28:11 -0800642 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'])
Jaewoong Jung2247d1d2019-06-18 13:10:28 -0700647 if args.assets_dir:
648 cmd.extend(['-A', args.assets_dir])
Jiyong Parkbb352472018-08-25 23:07:07 +0900649 cmd.extend(['-o', apk_file])
Alex Light9e6a2e62019-02-27 14:07:49 -0800650 cmd.extend(['-I', args.android_jar_path])
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900651 RunCommand(cmd, args.verbose)
652
Jiyong Parkbb352472018-08-25 23:07:07 +0900653 zip_file = os.path.join(work_dir, 'apex.zip')
654 cmd = ['soong_zip']
Jiyong Park34c821c2019-06-03 21:36:53 +0900655 cmd.append('-d') # include directories
656 cmd.extend(['-C', content_dir]) # relative root
657 cmd.extend(['-D', content_dir]) # input dir
Martijn Coenen09c106c2018-09-11 11:00:26 +0200658 for file_ in os.listdir(content_dir):
659 if os.path.isfile(os.path.join(content_dir, file_)):
Jiyong Park34c821c2019-06-03 21:36:53 +0900660 cmd.extend(['-s', file_]) # don't compress any files
Jiyong Parkbb352472018-08-25 23:07:07 +0900661 cmd.extend(['-o', zip_file])
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900662 RunCommand(cmd, args.verbose)
Jiyong Parkbb352472018-08-25 23:07:07 +0900663
664 unaligned_apex_file = os.path.join(work_dir, 'unaligned.apex')
665 cmd = ['merge_zips']
Jiyong Park34c821c2019-06-03 21:36:53 +0900666 cmd.append('-j') # sort
667 cmd.append(unaligned_apex_file) # output
668 cmd.append(apk_file) # input
669 cmd.append(zip_file) # input
Jiyong Parkbb352472018-08-25 23:07:07 +0900670 RunCommand(cmd, args.verbose)
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900671
Jiyong Park7c10f412018-08-21 21:28:41 +0900672 # Align the files at page boundary for efficient access
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900673 cmd = ['zipalign']
674 cmd.append('-f')
Jiyong Park4cd9b2b2019-01-19 13:42:35 +0900675 cmd.append(str(BLOCK_SIZE))
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900676 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):
Jiyong Park34c821c2019-06-03 21:36:53 +0900687
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900688 def __enter__(self):
689 self.name = tempfile.mkdtemp()
690 return self.name
691
Jiyong Park4edc6232018-09-03 12:31:58 +0900692 def __exit__(self, *unused):
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900693 shutil.rmtree(self.name)
694
695
696def main(argv):
Alex Light18a1b192019-03-04 14:27:28 -0800697 global tool_path_list
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900698 args = ParseArgs(argv)
Alex Light18a1b192019-03-04 14:27:28 -0800699 tool_path_list = args.apexer_tool_path
Jiyong Parkda6ab6e2018-08-14 23:44:51 +0900700 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:])