blob: 689e0951cc3233a52939dccc0e371f07c97961a7 [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 Peckham540d91a2019-04-25 14:18:16 -070083import subprocess
Bill Peckhame9eb5f92019-02-01 15:52:10 -080084import sys
85import zipfile
86
Bill Peckhame9eb5f92019-02-01 15:52:10 -080087import add_img_to_target_files
Daniel Normanf0318252019-04-15 11:34:56 -070088import build_super_image
89import common
Daniel Norman1bd2a1d2019-04-18 12:32:18 -070090import img_from_target_files
Daniel Norman3b64ce12019-04-16 16:11:35 -070091import ota_from_target_files
Bill Peckhame9eb5f92019-02-01 15:52:10 -080092
93logger = logging.getLogger(__name__)
94OPTIONS = common.OPTIONS
95OPTIONS.verbose = True
Bill Peckhamf753e152019-02-19 18:02:46 -080096OPTIONS.system_target_files = None
Daniel Norman2c99c5b2019-03-07 13:01:48 -080097OPTIONS.system_item_list = None
98OPTIONS.system_misc_info_keys = None
Bill Peckhamf753e152019-02-19 18:02:46 -080099OPTIONS.other_target_files = None
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800100OPTIONS.other_item_list = None
Bill Peckhamf753e152019-02-19 18:02:46 -0800101OPTIONS.output_target_files = None
Daniel Normanfdb38812019-04-15 09:47:24 -0700102OPTIONS.output_dir = None
103OPTIONS.output_item_list = None
Daniel Norman3b64ce12019-04-16 16:11:35 -0700104OPTIONS.output_ota = None
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700105OPTIONS.output_img = None
Daniel Normanf0318252019-04-15 11:34:56 -0700106OPTIONS.output_super_empty = None
Daniel Normana4911da2019-03-15 14:36:21 -0700107OPTIONS.rebuild_recovery = False
Bill Peckhamf753e152019-02-19 18:02:46 -0800108OPTIONS.keep_tmp = False
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800109
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800110# default_system_item_list is a list of items to extract from the partial
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800111# system target files package as is, meaning these items will land in the
112# output target files package exactly as they appear in the input partial
113# system target files package.
114
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800115default_system_item_list = [
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800116 'META/apkcerts.txt',
117 'META/filesystem_config.txt',
118 'META/root_filesystem_config.txt',
119 'META/system_manifest.xml',
120 'META/system_matrix.xml',
121 'META/update_engine_config.txt',
122 'PRODUCT/*',
123 'ROOT/*',
124 'SYSTEM/*',
125]
126
127# system_extract_special_item_list is a list of items to extract from the
128# partial system target files package that need some special processing, such
129# as some sort of combination with items from the partial other target files
130# package.
131
132system_extract_special_item_list = [
133 'META/*',
134]
135
Daniel Normane5b134a2019-04-17 14:54:06 -0700136# default_system_misc_info_keys is a list of keys to obtain from the system
137# instance of META/misc_info.txt. The remaining keys from the other instance.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800138
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800139default_system_misc_info_keys = [
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800140 'avb_system_hashtree_enable',
141 'avb_system_add_hashtree_footer_args',
142 'avb_system_key_path',
143 'avb_system_algorithm',
144 'avb_system_rollback_index_location',
145 'avb_product_hashtree_enable',
146 'avb_product_add_hashtree_footer_args',
147 'avb_product_services_hashtree_enable',
148 'avb_product_services_add_hashtree_footer_args',
149 'system_root_image',
150 'root_dir',
151 'ab_update',
152 'default_system_dev_certificate',
153 'system_size',
154]
155
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800156# default_other_item_list is a list of items to extract from the partial
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800157# other target files package as is, meaning these items will land in the output
158# target files package exactly as they appear in the input partial other target
159# files package.
160
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800161default_other_item_list = [
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800162 'META/boot_filesystem_config.txt',
163 'META/otakeys.txt',
164 'META/releasetools.py',
165 'META/vendor_filesystem_config.txt',
166 'META/vendor_manifest.xml',
167 'META/vendor_matrix.xml',
168 'BOOT/*',
169 'DATA/*',
170 'ODM/*',
171 'OTA/android-info.txt',
172 'PREBUILT_IMAGES/*',
173 'RADIO/*',
174 'VENDOR/*',
175]
176
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800177# other_extract_special_item_list is a list of items to extract from the
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800178# partial other target files package that need some special processing, such as
179# some sort of combination with items from the partial system target files
180# package.
181
182other_extract_special_item_list = [
183 'META/*',
184]
185
186
187def extract_items(target_files, target_files_temp_dir, extract_item_list):
188 """Extract items from target files to temporary directory.
189
190 This function extracts from the specified target files zip archive into the
191 specified temporary directory, the items specified in the extract item list.
192
193 Args:
194 target_files: The target files zip archive from which to extract items.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800195 target_files_temp_dir: The temporary directory where the extracted items
Daniel Normane5b134a2019-04-17 14:54:06 -0700196 will land.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800197 extract_item_list: A list of items to extract.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800198 """
199
200 logger.info('extracting from %s', target_files)
201
202 # Filter the extract_item_list to remove any items that do not exist in the
203 # zip file. Otherwise, the extraction step will fail.
204
205 with zipfile.ZipFile(
Daniel Normane5b134a2019-04-17 14:54:06 -0700206 target_files, 'r', allowZip64=True) as target_files_zipfile:
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800207 target_files_namelist = target_files_zipfile.namelist()
208
209 filtered_extract_item_list = []
210 for pattern in extract_item_list:
211 matching_namelist = fnmatch.filter(target_files_namelist, pattern)
212 if not matching_namelist:
213 logger.warning('no match for %s', pattern)
214 else:
215 filtered_extract_item_list.append(pattern)
216
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800217 # Extract from target_files into target_files_temp_dir the
218 # filtered_extract_item_list.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800219
Daniel Normane5b134a2019-04-17 14:54:06 -0700220 common.UnzipToDir(target_files, target_files_temp_dir,
221 filtered_extract_item_list)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800222
223
Daniel Normanfdb38812019-04-15 09:47:24 -0700224def copy_items(from_dir, to_dir, patterns):
225 """Similar to extract_items() except uses an input dir instead of zip."""
226 file_paths = []
227 for dirpath, _, filenames in os.walk(from_dir):
Daniel Normane5b134a2019-04-17 14:54:06 -0700228 file_paths.extend(
229 os.path.relpath(path=os.path.join(dirpath, filename), start=from_dir)
230 for filename in filenames)
Daniel Normanfdb38812019-04-15 09:47:24 -0700231
232 filtered_file_paths = set()
233 for pattern in patterns:
234 filtered_file_paths.update(fnmatch.filter(file_paths, pattern))
235
236 for file_path in filtered_file_paths:
237 original_file_path = os.path.join(from_dir, file_path)
238 copied_file_path = os.path.join(to_dir, file_path)
239 copied_file_dir = os.path.dirname(copied_file_path)
240 if not os.path.exists(copied_file_dir):
241 os.makedirs(copied_file_dir)
242 if os.path.islink(original_file_path):
243 os.symlink(os.readlink(original_file_path), copied_file_path)
244 else:
245 shutil.copyfile(original_file_path, copied_file_path)
246
247
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800248def read_config_list(config_file_path):
249 """Reads a config file into a list of strings.
250
251 Expects the file to be newline-separated.
252
253 Args:
254 config_file_path: The path to the config file to open and read.
Daniel Normane5b134a2019-04-17 14:54:06 -0700255
256 Returns:
257 The list of strings in the config file.
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800258 """
259 with open(config_file_path) as config_file:
260 return config_file.read().splitlines()
261
262
Daniel Normane5b134a2019-04-17 14:54:06 -0700263def validate_config_lists(system_item_list, system_misc_info_keys,
264 other_item_list):
Daniel Normane5964522019-03-19 10:32:03 -0700265 """Performs validations on the merge config lists.
266
267 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700268 system_item_list: The list of items to extract from the partial system
269 target files package as is.
270 system_misc_info_keys: A list of keys to obtain from the system instance of
271 META/misc_info.txt. The remaining keys from the other instance.
272 other_item_list: The list of items to extract from the partial other target
273 files package as is.
Daniel Normane5964522019-03-19 10:32:03 -0700274
275 Returns:
276 False if a validation fails, otherwise true.
277 """
278 default_combined_item_set = set(default_system_item_list)
279 default_combined_item_set.update(default_other_item_list)
280
281 combined_item_set = set(system_item_list)
282 combined_item_set.update(other_item_list)
283
284 # Check that the merge config lists are not missing any item specified
285 # by the default config lists.
286 difference = default_combined_item_set.difference(combined_item_set)
287 if difference:
Daniel Normane5b134a2019-04-17 14:54:06 -0700288 logger.error('Missing merge config items: %s', list(difference))
Daniel Normane5964522019-03-19 10:32:03 -0700289 logger.error('Please ensure missing items are in either the '
290 'system-item-list or other-item-list files provided to '
291 'this script.')
292 return False
293
Daniel Norman19b9fe92019-03-19 14:48:02 -0700294 if ('dynamic_partition_list' in system_misc_info_keys) or (
295 'super_partition_groups' in system_misc_info_keys):
296 logger.error('Dynamic partition misc info keys should come from '
297 'the other instance of META/misc_info.txt.')
298 return False
299
Daniel Normane5964522019-03-19 10:32:03 -0700300 return True
301
302
Daniel Normane5b134a2019-04-17 14:54:06 -0700303def process_ab_partitions_txt(system_target_files_temp_dir,
304 other_target_files_temp_dir,
305 output_target_files_temp_dir):
306 """Perform special processing for META/ab_partitions.txt.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800307
308 This function merges the contents of the META/ab_partitions.txt files from
309 the system directory and the other directory, placing the merged result in
310 the output directory. The precondition in that the files are already
311 extracted. The post condition is that the output META/ab_partitions.txt
312 contains the merged content. The format for each ab_partitions.txt a one
313 partition name per line. The output file contains the union of the parition
314 names.
315
316 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700317 system_target_files_temp_dir: The name of a directory containing the special
318 items extracted from the system target files package.
319 other_target_files_temp_dir: The name of a directory containing the special
320 items extracted from the other target files package.
321 output_target_files_temp_dir: The name of a directory that will be used to
322 create the output target files package after all the special cases are
323 processed.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800324 """
325
Daniel Normane5b134a2019-04-17 14:54:06 -0700326 system_ab_partitions_txt = os.path.join(system_target_files_temp_dir, 'META',
327 'ab_partitions.txt')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800328
Daniel Normane5b134a2019-04-17 14:54:06 -0700329 other_ab_partitions_txt = os.path.join(other_target_files_temp_dir, 'META',
330 'ab_partitions.txt')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800331
332 with open(system_ab_partitions_txt) as f:
333 system_ab_partitions = f.read().splitlines()
334
335 with open(other_ab_partitions_txt) as f:
336 other_ab_partitions = f.read().splitlines()
337
338 output_ab_partitions = set(system_ab_partitions + other_ab_partitions)
339
Daniel Normane5b134a2019-04-17 14:54:06 -0700340 output_ab_partitions_txt = os.path.join(output_target_files_temp_dir, 'META',
341 'ab_partitions.txt')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800342
343 with open(output_ab_partitions_txt, 'w') as output:
344 for partition in sorted(output_ab_partitions):
345 output.write('%s\n' % partition)
346
347
Bill Peckham364c1cc2019-03-29 18:27:23 -0700348def append_recovery_to_filesystem_config(output_target_files_temp_dir):
Daniel Normane5b134a2019-04-17 14:54:06 -0700349 """Perform special processing for META/filesystem_config.txt.
Bill Peckham364c1cc2019-03-29 18:27:23 -0700350
351 This function appends recovery information to META/filesystem_config.txt
352 so that recovery patch regeneration will succeed.
353
354 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700355 output_target_files_temp_dir: The name of a directory that will be used to
356 create the output target files package after all the special cases are
357 processed. We find filesystem_config.txt here.
Bill Peckham364c1cc2019-03-29 18:27:23 -0700358 """
359
Daniel Normane5b134a2019-04-17 14:54:06 -0700360 filesystem_config_txt = os.path.join(output_target_files_temp_dir, 'META',
361 'filesystem_config.txt')
Bill Peckham364c1cc2019-03-29 18:27:23 -0700362
363 with open(filesystem_config_txt, 'a') as f:
364 # TODO(bpeckham) this data is hard coded. It should be generated
365 # programmatically.
Daniel Normane5b134a2019-04-17 14:54:06 -0700366 f.write('system/bin/install-recovery.sh 0 0 750 '
367 'selabel=u:object_r:install_recovery_exec:s0 capabilities=0x0\n')
368 f.write('system/recovery-from-boot.p 0 0 644 '
369 'selabel=u:object_r:system_file:s0 capabilities=0x0\n')
370 f.write('system/etc/recovery.img 0 0 440 '
371 'selabel=u:object_r:install_recovery_exec:s0 capabilities=0x0\n')
Bill Peckham364c1cc2019-03-29 18:27:23 -0700372
373
Daniel Normane5b134a2019-04-17 14:54:06 -0700374def process_misc_info_txt(system_target_files_temp_dir,
375 other_target_files_temp_dir,
376 output_target_files_temp_dir, system_misc_info_keys):
377 """Perform special processing for META/misc_info.txt.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800378
379 This function merges the contents of the META/misc_info.txt files from the
380 system directory and the other directory, placing the merged result in the
381 output directory. The precondition in that the files are already extracted.
382 The post condition is that the output META/misc_info.txt contains the merged
383 content.
384
385 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700386 system_target_files_temp_dir: The name of a directory containing the special
387 items extracted from the system target files package.
388 other_target_files_temp_dir: The name of a directory containing the special
389 items extracted from the other target files package.
390 output_target_files_temp_dir: The name of a directory that will be used to
391 create the output target files package after all the special cases are
392 processed.
393 system_misc_info_keys: A list of keys to obtain from the system instance of
394 META/misc_info.txt. The remaining keys from the other instance.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800395 """
396
397 def read_helper(d):
398 misc_info_txt = os.path.join(d, 'META', 'misc_info.txt')
399 with open(misc_info_txt) as f:
400 return list(f.read().splitlines())
401
402 system_info_dict = common.LoadDictionaryFromLines(
403 read_helper(system_target_files_temp_dir))
404
405 # We take most of the misc info from the other target files.
406
407 merged_info_dict = common.LoadDictionaryFromLines(
408 read_helper(other_target_files_temp_dir))
409
410 # Replace certain values in merged_info_dict with values from
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800411 # system_info_dict.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800412
413 for key in system_misc_info_keys:
414 merged_info_dict[key] = system_info_dict[key]
415
Daniel Norman19b9fe92019-03-19 14:48:02 -0700416 # Merge misc info keys used for Dynamic Partitions.
417 if (merged_info_dict.get('use_dynamic_partitions') == 'true') and (
418 system_info_dict.get('use_dynamic_partitions') == 'true'):
419 merged_info_dict['dynamic_partition_list'] = '%s %s' % (
420 system_info_dict.get('dynamic_partition_list', ''),
421 merged_info_dict.get('dynamic_partition_list', ''))
422 # Partition groups and group sizes are defined by the other (non-system)
423 # misc info file because these values may vary for each board that uses
424 # a shared system image.
Daniel Normane5b134a2019-04-17 14:54:06 -0700425 for partition_group in merged_info_dict['super_partition_groups'].split(
426 ' '):
Daniel Norman19b9fe92019-03-19 14:48:02 -0700427 if ('super_%s_group_size' % partition_group) not in merged_info_dict:
Daniel Normanf0318252019-04-15 11:34:56 -0700428 raise ValueError(
Daniel Norman19b9fe92019-03-19 14:48:02 -0700429 'Other META/misc_info.txt does not contain required key '
430 'super_%s_group_size.' % partition_group)
431 key = 'super_%s_partition_list' % partition_group
Daniel Normane5b134a2019-04-17 14:54:06 -0700432 merged_info_dict[key] = '%s %s' % (system_info_dict.get(
433 key, ''), merged_info_dict.get(key, ''))
Daniel Norman19b9fe92019-03-19 14:48:02 -0700434
Daniel Normane5b134a2019-04-17 14:54:06 -0700435 output_misc_info_txt = os.path.join(output_target_files_temp_dir, 'META',
436 'misc_info.txt')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800437
438 sorted_keys = sorted(merged_info_dict.keys())
439
440 with open(output_misc_info_txt, 'w') as output:
441 for key in sorted_keys:
442 output.write('{}={}\n'.format(key, merged_info_dict[key]))
443
444
445def process_file_contexts_bin(temp_dir, output_target_files_temp_dir):
446 """Perform special processing for META/file_contexts.bin.
447
448 This function combines plat_file_contexts and vendor_file_contexts, which are
449 expected to already be extracted in temp_dir, to produce a merged
450 file_contexts.bin that will land in temp_dir at META/file_contexts.bin.
451
452 Args:
453 temp_dir: The name of a scratch directory that this function can use for
Daniel Normane5b134a2019-04-17 14:54:06 -0700454 intermediate files generated during processing.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800455 output_target_files_temp_dir: The name of the working directory that must
Daniel Normane5b134a2019-04-17 14:54:06 -0700456 already contain plat_file_contexts and vendor_file_contexts (in the
457 appropriate sub directories), and to which META/file_contexts.bin will be
458 written.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800459 """
460
461 # To create a merged file_contexts.bin file, we use the system and vendor
462 # file contexts files as input, the m4 tool to combine them, the sorting tool
463 # to sort, and finally the sefcontext_compile tool to generate the final
464 # output. We currently omit a checkfc step since the files had been checked
465 # as part of the build.
466
467 # The m4 step concatenates the two input files contexts files. Since m4
468 # writes to stdout, we receive that into an array of bytes, and then write it
469 # to a file.
470
471 # Collect the file contexts that we're going to combine from SYSTEM, VENDOR,
472 # PRODUCT, and ODM. We require SYSTEM and VENDOR, but others are optional.
473
474 file_contexts_list = []
475
476 for partition in ['SYSTEM', 'VENDOR', 'PRODUCT', 'ODM']:
477 prefix = 'plat' if partition == 'SYSTEM' else partition.lower()
478
Daniel Normane5b134a2019-04-17 14:54:06 -0700479 file_contexts = os.path.join(output_target_files_temp_dir, partition, 'etc',
480 'selinux', prefix + '_file_contexts')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800481
482 mandatory = partition in ['SYSTEM', 'VENDOR']
483
484 if mandatory or os.path.isfile(file_contexts):
485 file_contexts_list.append(file_contexts)
486 else:
487 logger.warning('file not found: %s', file_contexts)
488
489 command = ['m4', '--fatal-warnings', '-s'] + file_contexts_list
490
491 merged_content = common.RunAndCheckOutput(command, verbose=False)
492
493 merged_file_contexts_txt = os.path.join(temp_dir, 'merged_file_contexts.txt')
494
495 with open(merged_file_contexts_txt, 'wb') as f:
496 f.write(merged_content)
497
498 # The sort step sorts the concatenated file.
499
500 sorted_file_contexts_txt = os.path.join(temp_dir, 'sorted_file_contexts.txt')
501 command = ['fc_sort', merged_file_contexts_txt, sorted_file_contexts_txt]
Bill Peckham889b0c62019-02-21 18:53:37 -0800502 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800503
504 # Finally, the compile step creates the final META/file_contexts.bin.
505
Daniel Normane5b134a2019-04-17 14:54:06 -0700506 file_contexts_bin = os.path.join(output_target_files_temp_dir, 'META',
507 'file_contexts.bin')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800508
509 command = [
510 'sefcontext_compile',
Daniel Normane5b134a2019-04-17 14:54:06 -0700511 '-o',
512 file_contexts_bin,
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800513 sorted_file_contexts_txt,
514 ]
515
Bill Peckham889b0c62019-02-21 18:53:37 -0800516 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800517
518
Daniel Normane5b134a2019-04-17 14:54:06 -0700519def process_special_cases(temp_dir, system_target_files_temp_dir,
520 other_target_files_temp_dir,
521 output_target_files_temp_dir, system_misc_info_keys,
522 rebuild_recovery):
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800523 """Perform special-case processing for certain target files items.
524
525 Certain files in the output target files package require special-case
526 processing. This function performs all that special-case processing.
527
528 Args:
529 temp_dir: The name of a scratch directory that this function can use for
Daniel Normane5b134a2019-04-17 14:54:06 -0700530 intermediate files generated during processing.
531 system_target_files_temp_dir: The name of a directory containing the special
532 items extracted from the system target files package.
533 other_target_files_temp_dir: The name of a directory containing the special
534 items extracted from the other target files package.
535 output_target_files_temp_dir: The name of a directory that will be used to
536 create the output target files package after all the special cases are
537 processed.
538 system_misc_info_keys: A list of keys to obtain from the system instance of
539 META/misc_info.txt. The remaining keys from the other instance.
Bill Peckham364c1cc2019-03-29 18:27:23 -0700540 rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
Daniel Normane5b134a2019-04-17 14:54:06 -0700541 devices and write it to the system image.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800542 """
543
Bill Peckham364c1cc2019-03-29 18:27:23 -0700544 if 'ab_update' in system_misc_info_keys:
545 process_ab_partitions_txt(
546 system_target_files_temp_dir=system_target_files_temp_dir,
547 other_target_files_temp_dir=other_target_files_temp_dir,
548 output_target_files_temp_dir=output_target_files_temp_dir)
549
550 if rebuild_recovery:
551 append_recovery_to_filesystem_config(
552 output_target_files_temp_dir=output_target_files_temp_dir)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800553
554 process_misc_info_txt(
555 system_target_files_temp_dir=system_target_files_temp_dir,
556 other_target_files_temp_dir=other_target_files_temp_dir,
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800557 output_target_files_temp_dir=output_target_files_temp_dir,
558 system_misc_info_keys=system_misc_info_keys)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800559
Bill Peckham889b0c62019-02-21 18:53:37 -0800560 process_file_contexts_bin(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800561 temp_dir=temp_dir,
562 output_target_files_temp_dir=output_target_files_temp_dir)
563
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800564
Daniel Normane5b134a2019-04-17 14:54:06 -0700565def merge_target_files(temp_dir, system_target_files, system_item_list,
566 system_misc_info_keys, other_target_files,
567 other_item_list, output_target_files, output_dir,
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700568 output_item_list, output_ota, output_img,
569 output_super_empty, rebuild_recovery):
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800570 """Merge two target files packages together.
571
572 This function takes system and other target files packages as input, performs
573 various file extractions, special case processing, and finally creates a
574 merged zip archive as output.
575
576 Args:
577 temp_dir: The name of a directory we use when we extract items from the
Daniel Normane5b134a2019-04-17 14:54:06 -0700578 input target files packages, and also a scratch directory that we use for
579 temporary files.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800580 system_target_files: The name of the zip archive containing the system
Daniel Normane5b134a2019-04-17 14:54:06 -0700581 partial target files package.
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800582 system_item_list: The list of items to extract from the partial system
Daniel Normane5b134a2019-04-17 14:54:06 -0700583 target files package as is, meaning these items will land in the output
584 target files package exactly as they appear in the input partial system
585 target files package.
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800586 system_misc_info_keys: The list of keys to obtain from the system instance
Daniel Normane5b134a2019-04-17 14:54:06 -0700587 of META/misc_info.txt. The remaining keys from the other instance.
588 other_target_files: The name of the zip archive containing the other partial
589 target files package.
590 other_item_list: The list of items to extract from the partial other target
591 files package as is, meaning these items will land in the output target
592 files package exactly as they appear in the input partial other target
593 files package.
594 output_target_files: The name of the output zip archive target files package
595 created by merging system and other.
596 output_dir: The destination directory for saving merged files.
597 output_item_list: The list of items to copy into the output_dir.
Daniel Norman3b64ce12019-04-16 16:11:35 -0700598 output_ota: The name of the output zip archive ota package.
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700599 output_img: The name of the output zip archive img package.
Daniel Normanf0318252019-04-15 11:34:56 -0700600 output_super_empty: If provided, creates a super_empty.img file from the
Daniel Normane5b134a2019-04-17 14:54:06 -0700601 merged target files package and saves it at this path.
Daniel Normana4911da2019-03-15 14:36:21 -0700602 rebuild_recovery: If true, rebuild the recovery patch used by non-A/B
Daniel Normane5b134a2019-04-17 14:54:06 -0700603 devices and write it to the system image.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800604 """
605
Daniel Normane5b134a2019-04-17 14:54:06 -0700606 logger.info('starting: merge system %s and other %s into output %s',
607 system_target_files, other_target_files, output_target_files)
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800608
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800609 # Create directory names that we'll use when we extract files from system,
610 # and other, and for zipping the final output.
611
612 system_target_files_temp_dir = os.path.join(temp_dir, 'system')
613 other_target_files_temp_dir = os.path.join(temp_dir, 'other')
614 output_target_files_temp_dir = os.path.join(temp_dir, 'output')
615
616 # Extract "as is" items from the input system partial target files package.
617 # We extract them directly into the output temporary directory since the
618 # items do not need special case processing.
619
Bill Peckham889b0c62019-02-21 18:53:37 -0800620 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800621 target_files=system_target_files,
622 target_files_temp_dir=output_target_files_temp_dir,
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800623 extract_item_list=system_item_list)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800624
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800625 # Extract "as is" items from the input other partial target files package. We
626 # extract them directly into the output temporary directory since the items
627 # do not need special case processing.
628
Bill Peckham889b0c62019-02-21 18:53:37 -0800629 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800630 target_files=other_target_files,
631 target_files_temp_dir=output_target_files_temp_dir,
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800632 extract_item_list=other_item_list)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800633
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800634 # Extract "special" items from the input system partial target files package.
635 # We extract these items to different directory since they require special
636 # processing before they will end up in the output directory.
637
Bill Peckham889b0c62019-02-21 18:53:37 -0800638 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800639 target_files=system_target_files,
640 target_files_temp_dir=system_target_files_temp_dir,
641 extract_item_list=system_extract_special_item_list)
642
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800643 # Extract "special" items from the input other partial target files package.
644 # We extract these items to different directory since they require special
645 # processing before they will end up in the output directory.
646
Bill Peckham889b0c62019-02-21 18:53:37 -0800647 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800648 target_files=other_target_files,
649 target_files_temp_dir=other_target_files_temp_dir,
650 extract_item_list=other_extract_special_item_list)
651
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800652 # Now that the temporary directories contain all the extracted files, perform
653 # special case processing on any items that need it. After this function
654 # completes successfully, all the files we need to create the output target
655 # files package are in place.
656
Bill Peckham889b0c62019-02-21 18:53:37 -0800657 process_special_cases(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800658 temp_dir=temp_dir,
659 system_target_files_temp_dir=system_target_files_temp_dir,
660 other_target_files_temp_dir=other_target_files_temp_dir,
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800661 output_target_files_temp_dir=output_target_files_temp_dir,
Bill Peckham364c1cc2019-03-29 18:27:23 -0700662 system_misc_info_keys=system_misc_info_keys,
663 rebuild_recovery=rebuild_recovery)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800664
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800665 # Regenerate IMAGES in the temporary directory.
666
Daniel Normana4911da2019-03-15 14:36:21 -0700667 add_img_args = ['--verbose']
668 if rebuild_recovery:
669 add_img_args.append('--rebuild_recovery')
670 add_img_args.append(output_target_files_temp_dir)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800671
672 add_img_to_target_files.main(add_img_args)
673
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700674 # Create super_empty.img using the merged misc_info.txt.
675
676 misc_info_txt = os.path.join(output_target_files_temp_dir, 'META',
677 'misc_info.txt')
678
679 def read_helper():
680 with open(misc_info_txt) as f:
681 return list(f.read().splitlines())
682
683 use_dynamic_partitions = common.LoadDictionaryFromLines(
684 read_helper()).get('use_dynamic_partitions')
685
686 if use_dynamic_partitions != 'true' and output_super_empty:
687 raise ValueError(
688 'Building super_empty.img requires use_dynamic_partitions=true.')
689 elif use_dynamic_partitions == 'true':
690 super_empty_img = os.path.join(output_target_files_temp_dir, 'IMAGES',
691 'super_empty.img')
692 build_super_image_args = [
693 misc_info_txt,
694 super_empty_img,
695 ]
696 build_super_image.main(build_super_image_args)
697
698 # Copy super_empty.img to the user-provided output_super_empty location.
699 if output_super_empty:
700 shutil.copyfile(super_empty_img, output_super_empty)
701
Daniel Normanb8a2f9d2019-04-24 12:55:51 -0700702 # Create the IMG package from the merged target files (before zipping, in
703 # order to avoid an unnecessary unzip and copy).
704
705 if output_img:
706 img_from_target_files_args = [
707 output_target_files_temp_dir,
708 output_img,
709 ]
710 img_from_target_files.main(img_from_target_files_args)
711
Daniel Normanfdb38812019-04-15 09:47:24 -0700712 # Finally, create the output target files zip archive and/or copy the
713 # output items to the output target files directory.
714
715 if output_dir:
716 copy_items(output_target_files_temp_dir, output_dir, output_item_list)
717
718 if not output_target_files:
719 return
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800720
721 output_zip = os.path.abspath(output_target_files)
722 output_target_files_list = os.path.join(temp_dir, 'output.list')
Daniel Normane5b134a2019-04-17 14:54:06 -0700723 output_target_files_meta_dir = os.path.join(output_target_files_temp_dir,
724 'META')
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800725
Bill Peckham9662cfb2019-04-24 17:59:01 -0700726 find_command = [
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800727 'find',
728 output_target_files_meta_dir,
729 ]
Bill Peckham9662cfb2019-04-24 17:59:01 -0700730 find_process = common.Run(find_command, stdout=subprocess.PIPE, verbose=False)
731 meta_content = common.RunAndCheckOutput(['sort'], stdin=find_process.stdout,
732 verbose=False)
733
734 find_command = [
Daniel Normane5b134a2019-04-17 14:54:06 -0700735 'find', output_target_files_temp_dir, '-path',
736 output_target_files_meta_dir, '-prune', '-o', '-print'
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800737 ]
Bill Peckham9662cfb2019-04-24 17:59:01 -0700738 find_process = common.Run(find_command, stdout=subprocess.PIPE, verbose=False)
739 other_content = common.RunAndCheckOutput(['sort'], stdin=find_process.stdout,
740 verbose=False)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800741
742 with open(output_target_files_list, 'wb') as f:
743 f.write(meta_content)
744 f.write(other_content)
745
746 command = [
Bill Peckhamf753e152019-02-19 18:02:46 -0800747 'soong_zip',
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800748 '-d',
Daniel Normane5b134a2019-04-17 14:54:06 -0700749 '-o',
750 output_zip,
751 '-C',
752 output_target_files_temp_dir,
753 '-l',
754 output_target_files_list,
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800755 ]
756 logger.info('creating %s', output_target_files)
Bill Peckham889b0c62019-02-21 18:53:37 -0800757 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800758
Daniel Norman3b64ce12019-04-16 16:11:35 -0700759 # Create the OTA package from the merged target files package.
760
761 if output_ota:
762 ota_from_target_files_args = [
763 output_zip,
764 output_ota,
765 ]
766 ota_from_target_files.main(ota_from_target_files_args)
767
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700768
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800769
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800770def call_func_with_temp_dir(func, keep_tmp):
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800771 """Manage the creation and cleanup of the temporary directory.
772
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800773 This function calls the given function after first creating a temporary
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800774 directory. It also cleans up the temporary directory.
775
776 Args:
Daniel Normane5b134a2019-04-17 14:54:06 -0700777 func: The function to call. Should accept one parameter, the path to the
778 temporary directory.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800779 keep_tmp: Keep the temporary directory after processing is complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800780 """
781
782 # Create a temporary directory. This will serve as the parent of directories
783 # we use when we extract items from the input target files packages, and also
784 # a scratch directory that we use for temporary files.
785
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800786 temp_dir = common.MakeTempDir(prefix='merge_target_files_')
787
788 try:
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800789 func(temp_dir)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800790 except:
791 raise
792 finally:
793 if keep_tmp:
794 logger.info('keeping %s', temp_dir)
795 else:
796 common.Cleanup()
797
798
799def main():
800 """The main function.
801
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800802 Process command line arguments, then call merge_target_files to
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800803 perform the heavy lifting.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800804 """
805
806 common.InitLogging()
807
Bill Peckhamf753e152019-02-19 18:02:46 -0800808 def option_handler(o, a):
809 if o == '--system-target-files':
810 OPTIONS.system_target_files = a
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800811 elif o == '--system-item-list':
812 OPTIONS.system_item_list = a
813 elif o == '--system-misc-info-keys':
814 OPTIONS.system_misc_info_keys = a
Bill Peckhamf753e152019-02-19 18:02:46 -0800815 elif o == '--other-target-files':
816 OPTIONS.other_target_files = a
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800817 elif o == '--other-item-list':
818 OPTIONS.other_item_list = a
Bill Peckhamf753e152019-02-19 18:02:46 -0800819 elif o == '--output-target-files':
820 OPTIONS.output_target_files = a
Daniel Normanfdb38812019-04-15 09:47:24 -0700821 elif o == '--output-dir':
822 OPTIONS.output_dir = a
823 elif o == '--output-item-list':
824 OPTIONS.output_item_list = a
Daniel Norman3b64ce12019-04-16 16:11:35 -0700825 elif o == '--output-ota':
826 OPTIONS.output_ota = a
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700827 elif o == '--output-img':
828 OPTIONS.output_img = a
Daniel Normanf0318252019-04-15 11:34:56 -0700829 elif o == '--output-super-empty':
830 OPTIONS.output_super_empty = a
Daniel Normana4911da2019-03-15 14:36:21 -0700831 elif o == '--rebuild_recovery':
832 OPTIONS.rebuild_recovery = True
Bill Peckham364c1cc2019-03-29 18:27:23 -0700833 elif o == '--keep-tmp':
Bill Peckhamf753e152019-02-19 18:02:46 -0800834 OPTIONS.keep_tmp = True
835 else:
836 return False
837 return True
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800838
Bill Peckhamf753e152019-02-19 18:02:46 -0800839 args = common.ParseOptions(
Daniel Normane5b134a2019-04-17 14:54:06 -0700840 sys.argv[1:],
841 __doc__,
Bill Peckhamf753e152019-02-19 18:02:46 -0800842 extra_long_opts=[
843 'system-target-files=',
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800844 'system-item-list=',
845 'system-misc-info-keys=',
Bill Peckhamf753e152019-02-19 18:02:46 -0800846 'other-target-files=',
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800847 'other-item-list=',
Bill Peckhamf753e152019-02-19 18:02:46 -0800848 'output-target-files=',
Daniel Normanfdb38812019-04-15 09:47:24 -0700849 'output-dir=',
850 'output-item-list=',
Daniel Norman3b64ce12019-04-16 16:11:35 -0700851 'output-ota=',
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700852 'output-img=',
Daniel Normanf0318252019-04-15 11:34:56 -0700853 'output-super-empty=',
Daniel Normana4911da2019-03-15 14:36:21 -0700854 'rebuild_recovery',
Bill Peckham364c1cc2019-03-29 18:27:23 -0700855 'keep-tmp',
Bill Peckhamf753e152019-02-19 18:02:46 -0800856 ],
857 extra_option_handler=option_handler)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800858
Daniel Normane5b134a2019-04-17 14:54:06 -0700859 if (args or OPTIONS.system_target_files is None or
860 OPTIONS.other_target_files is None or
861 (OPTIONS.output_target_files is None and OPTIONS.output_dir is None) or
862 (OPTIONS.output_dir is not None and OPTIONS.output_item_list is None)):
Bill Peckhamf753e152019-02-19 18:02:46 -0800863 common.Usage(__doc__)
Bill Peckham889b0c62019-02-21 18:53:37 -0800864 sys.exit(1)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800865
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800866 if OPTIONS.system_item_list:
867 system_item_list = read_config_list(OPTIONS.system_item_list)
868 else:
869 system_item_list = default_system_item_list
870
871 if OPTIONS.system_misc_info_keys:
872 system_misc_info_keys = read_config_list(OPTIONS.system_misc_info_keys)
873 else:
874 system_misc_info_keys = default_system_misc_info_keys
875
876 if OPTIONS.other_item_list:
877 other_item_list = read_config_list(OPTIONS.other_item_list)
878 else:
879 other_item_list = default_other_item_list
880
Daniel Normanfdb38812019-04-15 09:47:24 -0700881 if OPTIONS.output_item_list:
882 output_item_list = read_config_list(OPTIONS.output_item_list)
883 else:
884 output_item_list = None
885
Daniel Normane5964522019-03-19 10:32:03 -0700886 if not validate_config_lists(
887 system_item_list=system_item_list,
Daniel Norman19b9fe92019-03-19 14:48:02 -0700888 system_misc_info_keys=system_misc_info_keys,
Daniel Normane5964522019-03-19 10:32:03 -0700889 other_item_list=other_item_list):
890 sys.exit(1)
891
Daniel Norman2c99c5b2019-03-07 13:01:48 -0800892 call_func_with_temp_dir(
893 lambda temp_dir: merge_target_files(
894 temp_dir=temp_dir,
895 system_target_files=OPTIONS.system_target_files,
896 system_item_list=system_item_list,
897 system_misc_info_keys=system_misc_info_keys,
898 other_target_files=OPTIONS.other_target_files,
899 other_item_list=other_item_list,
Daniel Normana4911da2019-03-15 14:36:21 -0700900 output_target_files=OPTIONS.output_target_files,
Daniel Normanfdb38812019-04-15 09:47:24 -0700901 output_dir=OPTIONS.output_dir,
902 output_item_list=output_item_list,
Daniel Norman3b64ce12019-04-16 16:11:35 -0700903 output_ota=OPTIONS.output_ota,
Daniel Norman1bd2a1d2019-04-18 12:32:18 -0700904 output_img=OPTIONS.output_img,
Daniel Normanf0318252019-04-15 11:34:56 -0700905 output_super_empty=OPTIONS.output_super_empty,
Daniel Normane5b134a2019-04-17 14:54:06 -0700906 rebuild_recovery=OPTIONS.rebuild_recovery), OPTIONS.keep_tmp)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800907
908
909if __name__ == '__main__':
Bill Peckham889b0c62019-02-21 18:53:37 -0800910 main()