blob: 1af532dc4e2a92bf9377a73dcbaa30b578f4a728 [file] [log] [blame]
Bill Peckhame9eb5f92019-02-01 15:52:10 -08001#!/usr/bin/env python
2#
3# Copyright (C) 2019 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may not
6# use this file except in compliance with the License. You may obtain a copy of
7# 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, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations under
15# the License.
Daniel Normane5b134a2019-04-17 14:54:06 -070016"""This script merges two partial target files packages.
Bill Peckhame9eb5f92019-02-01 15:52:10 -080017
Daniel Normane5b134a2019-04-17 14:54:06 -070018One package contains system files, and the other contains non-system files.
19It produces a complete target files package that can be used to generate an
20OTA package.
Bill Peckhame9eb5f92019-02-01 15:52:10 -080021
22Usage: merge_target_files.py [args]
23
24 --system-target-files system-target-files-zip-archive
25 The input target files package containing system bits. This is a zip
26 archive.
27
Daniel Norman2c99c5b2019-03-07 13:01:48 -080028 --system-item-list system-item-list-file
29 The optional path to a newline-separated config file that replaces the
30 contents of default_system_item_list if provided.
31
32 --system-misc-info-keys system-misc-info-keys-file
33 The optional path to a newline-separated config file that replaces the
34 contents of default_system_misc_info_keys if provided.
35
Bill Peckhame9eb5f92019-02-01 15:52:10 -080036 --other-target-files other-target-files-zip-archive
37 The input target files package containing other bits. This is a zip
38 archive.
39
Daniel Norman2c99c5b2019-03-07 13:01:48 -080040 --other-item-list other-item-list-file
41 The optional path to a newline-separated config file that replaces the
42 contents of default_other_item_list if provided.
43
Bill Peckhame9eb5f92019-02-01 15:52:10 -080044 --output-target-files output-target-files-package
Daniel Normanfdb38812019-04-15 09:47:24 -070045 If provided, the output merged target files package. Also a zip archive.
46
47 --output-dir output-directory
48 If provided, the destination directory for saving merged files. Requires
49 the --output-item-list flag.
50 Can be provided alongside --output-target-files, or by itself.
51
52 --output-item-list output-item-list-file.
53 The optional path to a newline-separated config file that specifies the
54 file patterns to copy into the --output-dir. Required if providing
55 the --output-dir flag.
Daniel Normana4911da2019-03-15 14:36:21 -070056
Daniel Norman3b64ce12019-04-16 16:11:35 -070057 --output-ota output-ota-package
58 The output ota package. This is a zip archive. Use of this flag may
59 require passing the --path common flag; see common.py.
60
Daniel Norman1bd2a1d2019-04-18 12:32:18 -070061 --output-img output-img-package
62 The output img package, suitable for use with 'fastboot update'. Use of
63 this flag may require passing the --path common flag; see common.py.
64
Daniel Normanf0318252019-04-15 11:34:56 -070065 --output-super-empty output-super-empty-image
66 If provided, creates a super_empty.img file from the merged target
67 files package and saves it at this path.
68
Daniel Normana4911da2019-03-15 14:36:21 -070069 --rebuild_recovery
70 Rebuild the recovery patch used by non-A/B devices and write it to the
71 system image.
Bill Peckham364c1cc2019-03-29 18:27:23 -070072
73 --keep-tmp
74 Keep tempoary files for debugging purposes.
Bill Peckhame9eb5f92019-02-01 15:52:10 -080075"""
76
77from __future__ import print_function
78
Bill Peckhame9eb5f92019-02-01 15:52:10 -080079import fnmatch
80import logging
81import os
Daniel Normanfdb38812019-04-15 09:47:24 -070082import shutil
Bill Peckhame9eb5f92019-02-01 15:52:10 -080083import sys
84import zipfile
85
Bill Peckhame9eb5f92019-02-01 15:52:10 -080086import add_img_to_target_files
Daniel Normanf0318252019-04-15 11:34:56 -070087import build_super_image
88import common
Daniel Norman1bd2a1d2019-04-18 12:32:18 -070089import img_from_target_files
Daniel Norman3b64ce12019-04-16 16:11:35 -070090import ota_from_target_files
Bill Peckhame9eb5f92019-02-01 15:52:10 -080091
92logger = logging.getLogger(__name__)
93OPTIONS = common.OPTIONS
94OPTIONS.verbose = True
Bill Peckhamf753e152019-02-19 18:02:46 -080095OPTIONS.system_target_files = None
Daniel Norman2c99c5b2019-03-07 13:01:48 -080096OPTIONS.system_item_list = None
97OPTIONS.system_misc_info_keys = None
Bill Peckhamf753e152019-02-19 18:02:46 -080098OPTIONS.other_target_files = None
Daniel Norman2c99c5b2019-03-07 13:01:48 -080099OPTIONS.other_item_list = None
Bill Peckhamf753e152019-02-19 18:02:46 -0800100OPTIONS.output_target_files = None
Daniel Normanfdb38812019-04-15 09:47:24 -0700101OPTIONS.output_dir = None
102OPTIONS.output_item_list = None
Daniel Norman3b64ce12019-04-16 16:11:35 -0700103OPTIONS.output_ota = None
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700104OPTIONS.output_img = None
Daniel Normanf0318252019-04-15 11:34:56 -0700105OPTIONS.output_super_empty = None
Daniel Normana4911da2019-03-15 14:36:21 -0700106OPTIONS.rebuild_recovery = False
Bill Peckhamf753e152019-02-19 18:02:46 -0800107OPTIONS.keep_tmp = False
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800108
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800109# default_system_item_list is a list of items to extract from the partial
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800110# system target files package as is, meaning these items will land in the
111# output target files package exactly as they appear in the input partial
112# system target files package.
113
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800114default_system_item_list = [
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800115 'META/apkcerts.txt',
116 'META/filesystem_config.txt',
117 'META/root_filesystem_config.txt',
118 'META/system_manifest.xml',
119 'META/system_matrix.xml',
120 'META/update_engine_config.txt',
121 'PRODUCT/*',
122 'ROOT/*',
123 'SYSTEM/*',
124]
125
126# system_extract_special_item_list is a list of items to extract from the
127# partial system target files package that need some special processing, such
128# as some sort of combination with items from the partial other target files
129# package.
130
131system_extract_special_item_list = [
132 'META/*',
133]
134
Daniel Normane5b134a2019-04-17 14:54:06 -0700135# default_system_misc_info_keys is a list of keys to obtain from the system
136# instance of META/misc_info.txt. The remaining keys from the other instance.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800137
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800138default_system_misc_info_keys = [
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800139 'avb_system_hashtree_enable',
140 'avb_system_add_hashtree_footer_args',
141 'avb_system_key_path',
142 'avb_system_algorithm',
143 'avb_system_rollback_index_location',
144 'avb_product_hashtree_enable',
145 'avb_product_add_hashtree_footer_args',
146 'avb_product_services_hashtree_enable',
147 'avb_product_services_add_hashtree_footer_args',
148 'system_root_image',
149 'root_dir',
150 'ab_update',
151 'default_system_dev_certificate',
152 'system_size',
153]
154
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800155# default_other_item_list is a list of items to extract from the partial
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800156# other target files package as is, meaning these items will land in the output
157# target files package exactly as they appear in the input partial other target
158# files package.
159
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800160default_other_item_list = [
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800161 'META/boot_filesystem_config.txt',
162 'META/otakeys.txt',
163 'META/releasetools.py',
164 'META/vendor_filesystem_config.txt',
165 'META/vendor_manifest.xml',
166 'META/vendor_matrix.xml',
167 'BOOT/*',
168 'DATA/*',
169 'ODM/*',
170 'OTA/android-info.txt',
171 'PREBUILT_IMAGES/*',
172 'RADIO/*',
173 'VENDOR/*',
174]
175
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800176# other_extract_special_item_list is a list of items to extract from the
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800177# partial other target files package that need some special processing, such as
178# some sort of combination with items from the partial system target files
179# package.
180
181other_extract_special_item_list = [
182 'META/*',
183]
184
185
186def extract_items(target_files, target_files_temp_dir, extract_item_list):
187 """Extract items from target files to temporary directory.
188
189 This function extracts from the specified target files zip archive into the
190 specified temporary directory, the items specified in the extract item list.
191
192 Args:
193 target_files: The target files zip archive from which to extract items.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800194 target_files_temp_dir: The temporary directory where the extracted items
Daniel Normane5b134a2019-04-17 14:54:06 -0700195 will land.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800196 extract_item_list: A list of items to extract.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800197 """
198
199 logger.info('extracting from %s', target_files)
200
201 # Filter the extract_item_list to remove any items that do not exist in the
202 # zip file. Otherwise, the extraction step will fail.
203
204 with zipfile.ZipFile(
Daniel Normane5b134a2019-04-17 14:54:06 -0700205 target_files, 'r', allowZip64=True) as target_files_zipfile:
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800206 target_files_namelist = target_files_zipfile.namelist()
207
208 filtered_extract_item_list = []
209 for pattern in extract_item_list:
210 matching_namelist = fnmatch.filter(target_files_namelist, pattern)
211 if not matching_namelist:
212 logger.warning('no match for %s', pattern)
213 else:
214 filtered_extract_item_list.append(pattern)
215
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800216 # Extract from target_files into target_files_temp_dir the
217 # filtered_extract_item_list.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800218
Daniel Normane5b134a2019-04-17 14:54:06 -0700219 common.UnzipToDir(target_files, target_files_temp_dir,
220 filtered_extract_item_list)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800221
222
Daniel Normanfdb38812019-04-15 09:47:24 -0700223def copy_items(from_dir, to_dir, patterns):
224 """Similar to extract_items() except uses an input dir instead of zip."""
225 file_paths = []
226 for dirpath, _, filenames in os.walk(from_dir):
Daniel Normane5b134a2019-04-17 14:54:06 -0700227 file_paths.extend(
228 os.path.relpath(path=os.path.join(dirpath, filename), start=from_dir)
229 for filename in filenames)
Daniel Normanfdb38812019-04-15 09:47:24 -0700230
231 filtered_file_paths = set()
232 for pattern in patterns:
233 filtered_file_paths.update(fnmatch.filter(file_paths, pattern))
234
235 for file_path in filtered_file_paths:
236 original_file_path = os.path.join(from_dir, file_path)
237 copied_file_path = os.path.join(to_dir, file_path)
238 copied_file_dir = os.path.dirname(copied_file_path)
239 if not os.path.exists(copied_file_dir):
240 os.makedirs(copied_file_dir)
241 if os.path.islink(original_file_path):
242 os.symlink(os.readlink(original_file_path), copied_file_path)
243 else:
244 shutil.copyfile(original_file_path, copied_file_path)
245
246
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800247def read_config_list(config_file_path):
248 """Reads a config file into a list of strings.
249
250 Expects the file to be newline-separated.
251
252 Args:
253 config_file_path: The path to the config file to open and read.
Daniel Normane5b134a2019-04-17 14:54:06 -0700254
255 Returns:
256 The list of strings in the config file.
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800257 """
258 with open(config_file_path) as config_file:
259 return config_file.read().splitlines()
260
261
Daniel Normane5b134a2019-04-17 14:54:06 -0700262def validate_config_lists(system_item_list, system_misc_info_keys,
263 other_item_list):
Daniel Normane5964522019-03-19 10:32:03 -0700264 """Performs validations on the merge config lists.
265
266 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700267 system_item_list: The list of items to extract from the partial system
268 target files package as is.
269 system_misc_info_keys: A list of keys to obtain from the system instance of
270 META/misc_info.txt. The remaining keys from the other instance.
271 other_item_list: The list of items to extract from the partial other target
272 files package as is.
Daniel Normane5964522019-03-19 10:32:03 -0700273
274 Returns:
275 False if a validation fails, otherwise true.
276 """
277 default_combined_item_set = set(default_system_item_list)
278 default_combined_item_set.update(default_other_item_list)
279
280 combined_item_set = set(system_item_list)
281 combined_item_set.update(other_item_list)
282
283 # Check that the merge config lists are not missing any item specified
284 # by the default config lists.
285 difference = default_combined_item_set.difference(combined_item_set)
286 if difference:
Daniel Normane5b134a2019-04-17 14:54:06 -0700287 logger.error('Missing merge config items: %s', list(difference))
Daniel Normane5964522019-03-19 10:32:03 -0700288 logger.error('Please ensure missing items are in either the '
289 'system-item-list or other-item-list files provided to '
290 'this script.')
291 return False
292
Daniel Norman19b9fe92019-03-19 14:48:02 -0700293 if ('dynamic_partition_list' in system_misc_info_keys) or (
294 'super_partition_groups' in system_misc_info_keys):
295 logger.error('Dynamic partition misc info keys should come from '
296 'the other instance of META/misc_info.txt.')
297 return False
298
Daniel Normane5964522019-03-19 10:32:03 -0700299 return True
300
301
Daniel Normane5b134a2019-04-17 14:54:06 -0700302def process_ab_partitions_txt(system_target_files_temp_dir,
303 other_target_files_temp_dir,
304 output_target_files_temp_dir):
305 """Perform special processing for META/ab_partitions.txt.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800306
307 This function merges the contents of the META/ab_partitions.txt files from
308 the system directory and the other directory, placing the merged result in
309 the output directory. The precondition in that the files are already
310 extracted. The post condition is that the output META/ab_partitions.txt
311 contains the merged content. The format for each ab_partitions.txt a one
312 partition name per line. The output file contains the union of the parition
313 names.
314
315 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700316 system_target_files_temp_dir: The name of a directory containing the special
317 items extracted from the system target files package.
318 other_target_files_temp_dir: The name of a directory containing the special
319 items extracted from the other target files package.
320 output_target_files_temp_dir: The name of a directory that will be used to
321 create the output target files package after all the special cases are
322 processed.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800323 """
324
Daniel Normane5b134a2019-04-17 14:54:06 -0700325 system_ab_partitions_txt = os.path.join(system_target_files_temp_dir, 'META',
326 'ab_partitions.txt')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800327
Daniel Normane5b134a2019-04-17 14:54:06 -0700328 other_ab_partitions_txt = os.path.join(other_target_files_temp_dir, 'META',
329 'ab_partitions.txt')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800330
331 with open(system_ab_partitions_txt) as f:
332 system_ab_partitions = f.read().splitlines()
333
334 with open(other_ab_partitions_txt) as f:
335 other_ab_partitions = f.read().splitlines()
336
337 output_ab_partitions = set(system_ab_partitions + other_ab_partitions)
338
Daniel Normane5b134a2019-04-17 14:54:06 -0700339 output_ab_partitions_txt = os.path.join(output_target_files_temp_dir, 'META',
340 'ab_partitions.txt')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800341
342 with open(output_ab_partitions_txt, 'w') as output:
343 for partition in sorted(output_ab_partitions):
344 output.write('%s\n' % partition)
345
346
Bill Peckham364c1cc2019-03-29 18:27:23 -0700347def append_recovery_to_filesystem_config(output_target_files_temp_dir):
Daniel Normane5b134a2019-04-17 14:54:06 -0700348 """Perform special processing for META/filesystem_config.txt.
Bill Peckham364c1cc2019-03-29 18:27:23 -0700349
350 This function appends recovery information to META/filesystem_config.txt
351 so that recovery patch regeneration will succeed.
352
353 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700354 output_target_files_temp_dir: The name of a directory that will be used to
355 create the output target files package after all the special cases are
356 processed. We find filesystem_config.txt here.
Bill Peckham364c1cc2019-03-29 18:27:23 -0700357 """
358
Daniel Normane5b134a2019-04-17 14:54:06 -0700359 filesystem_config_txt = os.path.join(output_target_files_temp_dir, 'META',
360 'filesystem_config.txt')
Bill Peckham364c1cc2019-03-29 18:27:23 -0700361
362 with open(filesystem_config_txt, 'a') as f:
363 # TODO(bpeckham) this data is hard coded. It should be generated
364 # programmatically.
Daniel Normane5b134a2019-04-17 14:54:06 -0700365 f.write('system/bin/install-recovery.sh 0 0 750 '
366 'selabel=u:object_r:install_recovery_exec:s0 capabilities=0x0\n')
367 f.write('system/recovery-from-boot.p 0 0 644 '
368 'selabel=u:object_r:system_file:s0 capabilities=0x0\n')
369 f.write('system/etc/recovery.img 0 0 440 '
370 'selabel=u:object_r:install_recovery_exec:s0 capabilities=0x0\n')
Bill Peckham364c1cc2019-03-29 18:27:23 -0700371
372
Daniel Normane5b134a2019-04-17 14:54:06 -0700373def process_misc_info_txt(system_target_files_temp_dir,
374 other_target_files_temp_dir,
375 output_target_files_temp_dir, system_misc_info_keys):
376 """Perform special processing for META/misc_info.txt.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800377
378 This function merges the contents of the META/misc_info.txt files from the
379 system directory and the other directory, placing the merged result in the
380 output directory. The precondition in that the files are already extracted.
381 The post condition is that the output META/misc_info.txt contains the merged
382 content.
383
384 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700385 system_target_files_temp_dir: The name of a directory containing the special
386 items extracted from the system target files package.
387 other_target_files_temp_dir: The name of a directory containing the special
388 items extracted from the other target files package.
389 output_target_files_temp_dir: The name of a directory that will be used to
390 create the output target files package after all the special cases are
391 processed.
392 system_misc_info_keys: A list of keys to obtain from the system instance of
393 META/misc_info.txt. The remaining keys from the other instance.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800394 """
395
396 def read_helper(d):
397 misc_info_txt = os.path.join(d, 'META', 'misc_info.txt')
398 with open(misc_info_txt) as f:
399 return list(f.read().splitlines())
400
401 system_info_dict = common.LoadDictionaryFromLines(
402 read_helper(system_target_files_temp_dir))
403
404 # We take most of the misc info from the other target files.
405
406 merged_info_dict = common.LoadDictionaryFromLines(
407 read_helper(other_target_files_temp_dir))
408
409 # Replace certain values in merged_info_dict with values from
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800410 # system_info_dict.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800411
412 for key in system_misc_info_keys:
413 merged_info_dict[key] = system_info_dict[key]
414
Daniel Norman19b9fe92019-03-19 14:48:02 -0700415 # Merge misc info keys used for Dynamic Partitions.
416 if (merged_info_dict.get('use_dynamic_partitions') == 'true') and (
417 system_info_dict.get('use_dynamic_partitions') == 'true'):
418 merged_info_dict['dynamic_partition_list'] = '%s %s' % (
419 system_info_dict.get('dynamic_partition_list', ''),
420 merged_info_dict.get('dynamic_partition_list', ''))
421 # Partition groups and group sizes are defined by the other (non-system)
422 # misc info file because these values may vary for each board that uses
423 # a shared system image.
Daniel Normane5b134a2019-04-17 14:54:06 -0700424 for partition_group in merged_info_dict['super_partition_groups'].split(
425 ' '):
Daniel Norman19b9fe92019-03-19 14:48:02 -0700426 if ('super_%s_group_size' % partition_group) not in merged_info_dict:
Daniel Normanf0318252019-04-15 11:34:56 -0700427 raise ValueError(
Daniel Norman19b9fe92019-03-19 14:48:02 -0700428 'Other META/misc_info.txt does not contain required key '
429 'super_%s_group_size.' % partition_group)
430 key = 'super_%s_partition_list' % partition_group
Daniel Normane5b134a2019-04-17 14:54:06 -0700431 merged_info_dict[key] = '%s %s' % (system_info_dict.get(
432 key, ''), merged_info_dict.get(key, ''))
Daniel Norman19b9fe92019-03-19 14:48:02 -0700433
Daniel Normane5b134a2019-04-17 14:54:06 -0700434 output_misc_info_txt = os.path.join(output_target_files_temp_dir, 'META',
435 'misc_info.txt')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800436
437 sorted_keys = sorted(merged_info_dict.keys())
438
439 with open(output_misc_info_txt, 'w') as output:
440 for key in sorted_keys:
441 output.write('{}={}\n'.format(key, merged_info_dict[key]))
442
443
444def process_file_contexts_bin(temp_dir, output_target_files_temp_dir):
445 """Perform special processing for META/file_contexts.bin.
446
447 This function combines plat_file_contexts and vendor_file_contexts, which are
448 expected to already be extracted in temp_dir, to produce a merged
449 file_contexts.bin that will land in temp_dir at META/file_contexts.bin.
450
451 Args:
452 temp_dir: The name of a scratch directory that this function can use for
Daniel Normane5b134a2019-04-17 14:54:06 -0700453 intermediate files generated during processing.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800454 output_target_files_temp_dir: The name of the working directory that must
Daniel Normane5b134a2019-04-17 14:54:06 -0700455 already contain plat_file_contexts and vendor_file_contexts (in the
456 appropriate sub directories), and to which META/file_contexts.bin will be
457 written.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800458 """
459
460 # To create a merged file_contexts.bin file, we use the system and vendor
461 # file contexts files as input, the m4 tool to combine them, the sorting tool
462 # to sort, and finally the sefcontext_compile tool to generate the final
463 # output. We currently omit a checkfc step since the files had been checked
464 # as part of the build.
465
466 # The m4 step concatenates the two input files contexts files. Since m4
467 # writes to stdout, we receive that into an array of bytes, and then write it
468 # to a file.
469
470 # Collect the file contexts that we're going to combine from SYSTEM, VENDOR,
471 # PRODUCT, and ODM. We require SYSTEM and VENDOR, but others are optional.
472
473 file_contexts_list = []
474
475 for partition in ['SYSTEM', 'VENDOR', 'PRODUCT', 'ODM']:
476 prefix = 'plat' if partition == 'SYSTEM' else partition.lower()
477
Daniel Normane5b134a2019-04-17 14:54:06 -0700478 file_contexts = os.path.join(output_target_files_temp_dir, partition, 'etc',
479 'selinux', prefix + '_file_contexts')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800480
481 mandatory = partition in ['SYSTEM', 'VENDOR']
482
483 if mandatory or os.path.isfile(file_contexts):
484 file_contexts_list.append(file_contexts)
485 else:
486 logger.warning('file not found: %s', file_contexts)
487
488 command = ['m4', '--fatal-warnings', '-s'] + file_contexts_list
489
490 merged_content = common.RunAndCheckOutput(command, verbose=False)
491
492 merged_file_contexts_txt = os.path.join(temp_dir, 'merged_file_contexts.txt')
493
494 with open(merged_file_contexts_txt, 'wb') as f:
495 f.write(merged_content)
496
497 # The sort step sorts the concatenated file.
498
499 sorted_file_contexts_txt = os.path.join(temp_dir, 'sorted_file_contexts.txt')
500 command = ['fc_sort', merged_file_contexts_txt, sorted_file_contexts_txt]
Bill Peckham889b0c62019-02-21 18:53:37 -0800501 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800502
503 # Finally, the compile step creates the final META/file_contexts.bin.
504
Daniel Normane5b134a2019-04-17 14:54:06 -0700505 file_contexts_bin = os.path.join(output_target_files_temp_dir, 'META',
506 'file_contexts.bin')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800507
508 command = [
509 'sefcontext_compile',
Daniel Normane5b134a2019-04-17 14:54:06 -0700510 '-o',
511 file_contexts_bin,
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800512 sorted_file_contexts_txt,
513 ]
514
Bill Peckham889b0c62019-02-21 18:53:37 -0800515 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800516
517
Daniel Normane5b134a2019-04-17 14:54:06 -0700518def process_special_cases(temp_dir, system_target_files_temp_dir,
519 other_target_files_temp_dir,
520 output_target_files_temp_dir, system_misc_info_keys,
521 rebuild_recovery):
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800522 """Perform special-case processing for certain target files items.
523
524 Certain files in the output target files package require special-case
525 processing. This function performs all that special-case processing.
526
527 Args:
528 temp_dir: The name of a scratch directory that this function can use for
Daniel Normane5b134a2019-04-17 14:54:06 -0700529 intermediate files generated during processing.
530 system_target_files_temp_dir: The name of a directory containing the special
531 items extracted from the system target files package.
532 other_target_files_temp_dir: The name of a directory containing the special
533 items extracted from the other target files package.
534 output_target_files_temp_dir: The name of a directory that will be used to
535 create the output target files package after all the special cases are
536 processed.
537 system_misc_info_keys: A list of keys to obtain from the system instance of
538 META/misc_info.txt. The remaining keys from the other instance.
Bill Peckham364c1cc2019-03-29 18:27:23 -0700539 rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
Daniel Normane5b134a2019-04-17 14:54:06 -0700540 devices and write it to the system image.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800541 """
542
Bill Peckham364c1cc2019-03-29 18:27:23 -0700543 if 'ab_update' in system_misc_info_keys:
544 process_ab_partitions_txt(
545 system_target_files_temp_dir=system_target_files_temp_dir,
546 other_target_files_temp_dir=other_target_files_temp_dir,
547 output_target_files_temp_dir=output_target_files_temp_dir)
548
549 if rebuild_recovery:
550 append_recovery_to_filesystem_config(
551 output_target_files_temp_dir=output_target_files_temp_dir)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800552
553 process_misc_info_txt(
554 system_target_files_temp_dir=system_target_files_temp_dir,
555 other_target_files_temp_dir=other_target_files_temp_dir,
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800556 output_target_files_temp_dir=output_target_files_temp_dir,
557 system_misc_info_keys=system_misc_info_keys)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800558
Bill Peckham889b0c62019-02-21 18:53:37 -0800559 process_file_contexts_bin(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800560 temp_dir=temp_dir,
561 output_target_files_temp_dir=output_target_files_temp_dir)
562
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800563
Daniel Normane5b134a2019-04-17 14:54:06 -0700564def merge_target_files(temp_dir, system_target_files, system_item_list,
565 system_misc_info_keys, other_target_files,
566 other_item_list, output_target_files, output_dir,
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700567 output_item_list, output_ota, output_img,
568 output_super_empty, rebuild_recovery):
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800569 """Merge two target files packages together.
570
571 This function takes system and other target files packages as input, performs
572 various file extractions, special case processing, and finally creates a
573 merged zip archive as output.
574
575 Args:
576 temp_dir: The name of a directory we use when we extract items from the
Daniel Normane5b134a2019-04-17 14:54:06 -0700577 input target files packages, and also a scratch directory that we use for
578 temporary files.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800579 system_target_files: The name of the zip archive containing the system
Daniel Normane5b134a2019-04-17 14:54:06 -0700580 partial target files package.
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800581 system_item_list: The list of items to extract from the partial system
Daniel Normane5b134a2019-04-17 14:54:06 -0700582 target files package as is, meaning these items will land in the output
583 target files package exactly as they appear in the input partial system
584 target files package.
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800585 system_misc_info_keys: The list of keys to obtain from the system instance
Daniel Normane5b134a2019-04-17 14:54:06 -0700586 of META/misc_info.txt. The remaining keys from the other instance.
587 other_target_files: The name of the zip archive containing the other partial
588 target files package.
589 other_item_list: The list of items to extract from the partial other target
590 files package as is, meaning these items will land in the output target
591 files package exactly as they appear in the input partial other target
592 files package.
593 output_target_files: The name of the output zip archive target files package
594 created by merging system and other.
595 output_dir: The destination directory for saving merged files.
596 output_item_list: The list of items to copy into the output_dir.
Daniel Norman3b64ce12019-04-16 16:11:35 -0700597 output_ota: The name of the output zip archive ota package.
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700598 output_img: The name of the output zip archive img package.
Daniel Normanf0318252019-04-15 11:34:56 -0700599 output_super_empty: If provided, creates a super_empty.img file from the
Daniel Normane5b134a2019-04-17 14:54:06 -0700600 merged target files package and saves it at this path.
Daniel Normana4911da2019-03-15 14:36:21 -0700601 rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
Daniel Normane5b134a2019-04-17 14:54:06 -0700602 devices and write it to the system image.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800603 """
604
Daniel Normane5b134a2019-04-17 14:54:06 -0700605 logger.info('starting: merge system %s and other %s into output %s',
606 system_target_files, other_target_files, output_target_files)
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800607
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800608 # Create directory names that we'll use when we extract files from system,
609 # and other, and for zipping the final output.
610
611 system_target_files_temp_dir = os.path.join(temp_dir, 'system')
612 other_target_files_temp_dir = os.path.join(temp_dir, 'other')
613 output_target_files_temp_dir = os.path.join(temp_dir, 'output')
614
615 # Extract "as is" items from the input system partial target files package.
616 # We extract them directly into the output temporary directory since the
617 # items do not need special case processing.
618
Bill Peckham889b0c62019-02-21 18:53:37 -0800619 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800620 target_files=system_target_files,
621 target_files_temp_dir=output_target_files_temp_dir,
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800622 extract_item_list=system_item_list)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800623
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800624 # Extract "as is" items from the input other partial target files package. We
625 # extract them directly into the output temporary directory since the items
626 # do not need special case processing.
627
Bill Peckham889b0c62019-02-21 18:53:37 -0800628 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800629 target_files=other_target_files,
630 target_files_temp_dir=output_target_files_temp_dir,
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800631 extract_item_list=other_item_list)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800632
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800633 # Extract "special" items from the input system partial target files package.
634 # We extract these items to different directory since they require special
635 # processing before they will end up in the output directory.
636
Bill Peckham889b0c62019-02-21 18:53:37 -0800637 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800638 target_files=system_target_files,
639 target_files_temp_dir=system_target_files_temp_dir,
640 extract_item_list=system_extract_special_item_list)
641
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800642 # Extract "special" items from the input other partial target files package.
643 # We extract these items to different directory since they require special
644 # processing before they will end up in the output directory.
645
Bill Peckham889b0c62019-02-21 18:53:37 -0800646 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800647 target_files=other_target_files,
648 target_files_temp_dir=other_target_files_temp_dir,
649 extract_item_list=other_extract_special_item_list)
650
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800651 # Now that the temporary directories contain all the extracted files, perform
652 # special case processing on any items that need it. After this function
653 # completes successfully, all the files we need to create the output target
654 # files package are in place.
655
Bill Peckham889b0c62019-02-21 18:53:37 -0800656 process_special_cases(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800657 temp_dir=temp_dir,
658 system_target_files_temp_dir=system_target_files_temp_dir,
659 other_target_files_temp_dir=other_target_files_temp_dir,
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800660 output_target_files_temp_dir=output_target_files_temp_dir,
Bill Peckham364c1cc2019-03-29 18:27:23 -0700661 system_misc_info_keys=system_misc_info_keys,
662 rebuild_recovery=rebuild_recovery)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800663
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800664 # Regenerate IMAGES in the temporary directory.
665
Daniel Normana4911da2019-03-15 14:36:21 -0700666 add_img_args = ['--verbose']
667 if rebuild_recovery:
668 add_img_args.append('--rebuild_recovery')
669 add_img_args.append(output_target_files_temp_dir)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800670
671 add_img_to_target_files.main(add_img_args)
672
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700673 # Create super_empty.img using the merged misc_info.txt.
674
675 misc_info_txt = os.path.join(output_target_files_temp_dir, 'META',
676 'misc_info.txt')
677
678 def read_helper():
679 with open(misc_info_txt) as f:
680 return list(f.read().splitlines())
681
682 use_dynamic_partitions = common.LoadDictionaryFromLines(
683 read_helper()).get('use_dynamic_partitions')
684
685 if use_dynamic_partitions != 'true' and output_super_empty:
686 raise ValueError(
687 'Building super_empty.img requires use_dynamic_partitions=true.')
688 elif use_dynamic_partitions == 'true':
689 super_empty_img = os.path.join(output_target_files_temp_dir, 'IMAGES',
690 'super_empty.img')
691 build_super_image_args = [
692 misc_info_txt,
693 super_empty_img,
694 ]
695 build_super_image.main(build_super_image_args)
696
697 # Copy super_empty.img to the user-provided output_super_empty location.
698 if output_super_empty:
699 shutil.copyfile(super_empty_img, output_super_empty)
700
Daniel Normanb8a2f9d2019-04-24 12:55:51 -0700701 # Create the IMG package from the merged target files (before zipping, in
702 # order to avoid an unnecessary unzip and copy).
703
704 if output_img:
705 img_from_target_files_args = [
706 output_target_files_temp_dir,
707 output_img,
708 ]
709 img_from_target_files.main(img_from_target_files_args)
710
Daniel Normanfdb38812019-04-15 09:47:24 -0700711 # Finally, create the output target files zip archive and/or copy the
712 # output items to the output target files directory.
713
714 if output_dir:
715 copy_items(output_target_files_temp_dir, output_dir, output_item_list)
716
717 if not output_target_files:
718 return
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800719
720 output_zip = os.path.abspath(output_target_files)
721 output_target_files_list = os.path.join(temp_dir, 'output.list')
Daniel Normane5b134a2019-04-17 14:54:06 -0700722 output_target_files_meta_dir = os.path.join(output_target_files_temp_dir,
723 'META')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800724
Bill Peckham9662cfb2019-04-24 17:59:01 -0700725 find_command = [
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800726 'find',
727 output_target_files_meta_dir,
728 ]
Bill Peckham9662cfb2019-04-24 17:59:01 -0700729 find_process = common.Run(find_command, stdout=subprocess.PIPE, verbose=False)
730 meta_content = common.RunAndCheckOutput(['sort'], stdin=find_process.stdout,
731 verbose=False)
732
733 find_command = [
Daniel Normane5b134a2019-04-17 14:54:06 -0700734 'find', output_target_files_temp_dir, '-path',
735 output_target_files_meta_dir, '-prune', '-o', '-print'
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800736 ]
Bill Peckham9662cfb2019-04-24 17:59:01 -0700737 find_process = common.Run(find_command, stdout=subprocess.PIPE, verbose=False)
738 other_content = common.RunAndCheckOutput(['sort'], stdin=find_process.stdout,
739 verbose=False)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800740
741 with open(output_target_files_list, 'wb') as f:
742 f.write(meta_content)
743 f.write(other_content)
744
745 command = [
Bill Peckhamf753e152019-02-19 18:02:46 -0800746 'soong_zip',
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800747 '-d',
Daniel Normane5b134a2019-04-17 14:54:06 -0700748 '-o',
749 output_zip,
750 '-C',
751 output_target_files_temp_dir,
752 '-l',
753 output_target_files_list,
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800754 ]
755 logger.info('creating %s', output_target_files)
Bill Peckham889b0c62019-02-21 18:53:37 -0800756 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800757
Daniel Norman3b64ce12019-04-16 16:11:35 -0700758 # Create the OTA package from the merged target files package.
759
760 if output_ota:
761 ota_from_target_files_args = [
762 output_zip,
763 output_ota,
764 ]
765 ota_from_target_files.main(ota_from_target_files_args)
766
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700767
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800768
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800769def call_func_with_temp_dir(func, keep_tmp):
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800770 """Manage the creation and cleanup of the temporary directory.
771
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800772 This function calls the given function after first creating a temporary
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800773 directory. It also cleans up the temporary directory.
774
775 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700776 func: The function to call. Should accept one parameter, the path to the
777 temporary directory.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800778 keep_tmp: Keep the temporary directory after processing is complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800779 """
780
781 # Create a temporary directory. This will serve as the parent of directories
782 # we use when we extract items from the input target files packages, and also
783 # a scratch directory that we use for temporary files.
784
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800785 temp_dir = common.MakeTempDir(prefix='merge_target_files_')
786
787 try:
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800788 func(temp_dir)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800789 except:
790 raise
791 finally:
792 if keep_tmp:
793 logger.info('keeping %s', temp_dir)
794 else:
795 common.Cleanup()
796
797
798def main():
799 """The main function.
800
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800801 Process command line arguments, then call merge_target_files to
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800802 perform the heavy lifting.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800803 """
804
805 common.InitLogging()
806
Bill Peckhamf753e152019-02-19 18:02:46 -0800807 def option_handler(o, a):
808 if o == '--system-target-files':
809 OPTIONS.system_target_files = a
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800810 elif o == '--system-item-list':
811 OPTIONS.system_item_list = a
812 elif o == '--system-misc-info-keys':
813 OPTIONS.system_misc_info_keys = a
Bill Peckhamf753e152019-02-19 18:02:46 -0800814 elif o == '--other-target-files':
815 OPTIONS.other_target_files = a
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800816 elif o == '--other-item-list':
817 OPTIONS.other_item_list = a
Bill Peckhamf753e152019-02-19 18:02:46 -0800818 elif o == '--output-target-files':
819 OPTIONS.output_target_files = a
Daniel Normanfdb38812019-04-15 09:47:24 -0700820 elif o == '--output-dir':
821 OPTIONS.output_dir = a
822 elif o == '--output-item-list':
823 OPTIONS.output_item_list = a
Daniel Norman3b64ce12019-04-16 16:11:35 -0700824 elif o == '--output-ota':
825 OPTIONS.output_ota = a
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700826 elif o == '--output-img':
827 OPTIONS.output_img = a
Daniel Normanf0318252019-04-15 11:34:56 -0700828 elif o == '--output-super-empty':
829 OPTIONS.output_super_empty = a
Daniel Normana4911da2019-03-15 14:36:21 -0700830 elif o == '--rebuild_recovery':
831 OPTIONS.rebuild_recovery = True
Bill Peckham364c1cc2019-03-29 18:27:23 -0700832 elif o == '--keep-tmp':
Bill Peckhamf753e152019-02-19 18:02:46 -0800833 OPTIONS.keep_tmp = True
834 else:
835 return False
836 return True
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800837
Bill Peckhamf753e152019-02-19 18:02:46 -0800838 args = common.ParseOptions(
Daniel Normane5b134a2019-04-17 14:54:06 -0700839 sys.argv[1:],
840 __doc__,
Bill Peckhamf753e152019-02-19 18:02:46 -0800841 extra_long_opts=[
842 'system-target-files=',
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800843 'system-item-list=',
844 'system-misc-info-keys=',
Bill Peckhamf753e152019-02-19 18:02:46 -0800845 'other-target-files=',
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800846 'other-item-list=',
Bill Peckhamf753e152019-02-19 18:02:46 -0800847 'output-target-files=',
Daniel Normanfdb38812019-04-15 09:47:24 -0700848 'output-dir=',
849 'output-item-list=',
Daniel Norman3b64ce12019-04-16 16:11:35 -0700850 'output-ota=',
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700851 'output-img=',
Daniel Normanf0318252019-04-15 11:34:56 -0700852 'output-super-empty=',
Daniel Normana4911da2019-03-15 14:36:21 -0700853 'rebuild_recovery',
Bill Peckham364c1cc2019-03-29 18:27:23 -0700854 'keep-tmp',
Bill Peckhamf753e152019-02-19 18:02:46 -0800855 ],
856 extra_option_handler=option_handler)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800857
Daniel Normane5b134a2019-04-17 14:54:06 -0700858 if (args or OPTIONS.system_target_files is None or
859 OPTIONS.other_target_files is None or
860 (OPTIONS.output_target_files is None and OPTIONS.output_dir is None) or
861 (OPTIONS.output_dir is not None and OPTIONS.output_item_list is None)):
Bill Peckhamf753e152019-02-19 18:02:46 -0800862 common.Usage(__doc__)
Bill Peckham889b0c62019-02-21 18:53:37 -0800863 sys.exit(1)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800864
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800865 if OPTIONS.system_item_list:
866 system_item_list = read_config_list(OPTIONS.system_item_list)
867 else:
868 system_item_list = default_system_item_list
869
870 if OPTIONS.system_misc_info_keys:
871 system_misc_info_keys = read_config_list(OPTIONS.system_misc_info_keys)
872 else:
873 system_misc_info_keys = default_system_misc_info_keys
874
875 if OPTIONS.other_item_list:
876 other_item_list = read_config_list(OPTIONS.other_item_list)
877 else:
878 other_item_list = default_other_item_list
879
Daniel Normanfdb38812019-04-15 09:47:24 -0700880 if OPTIONS.output_item_list:
881 output_item_list = read_config_list(OPTIONS.output_item_list)
882 else:
883 output_item_list = None
884
Daniel Normane5964522019-03-19 10:32:03 -0700885 if not validate_config_lists(
886 system_item_list=system_item_list,
Daniel Norman19b9fe92019-03-19 14:48:02 -0700887 system_misc_info_keys=system_misc_info_keys,
Daniel Normane5964522019-03-19 10:32:03 -0700888 other_item_list=other_item_list):
889 sys.exit(1)
890
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800891 call_func_with_temp_dir(
892 lambda temp_dir: merge_target_files(
893 temp_dir=temp_dir,
894 system_target_files=OPTIONS.system_target_files,
895 system_item_list=system_item_list,
896 system_misc_info_keys=system_misc_info_keys,
897 other_target_files=OPTIONS.other_target_files,
898 other_item_list=other_item_list,
Daniel Normana4911da2019-03-15 14:36:21 -0700899 output_target_files=OPTIONS.output_target_files,
Daniel Normanfdb38812019-04-15 09:47:24 -0700900 output_dir=OPTIONS.output_dir,
901 output_item_list=output_item_list,
Daniel Norman3b64ce12019-04-16 16:11:35 -0700902 output_ota=OPTIONS.output_ota,
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700903 output_img=OPTIONS.output_img,
Daniel Normanf0318252019-04-15 11:34:56 -0700904 output_super_empty=OPTIONS.output_super_empty,
Daniel Normane5b134a2019-04-17 14:54:06 -0700905 rebuild_recovery=OPTIONS.rebuild_recovery), OPTIONS.keep_tmp)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800906
907
908if __name__ == '__main__':
Bill Peckham889b0c62019-02-21 18:53:37 -0800909 main()