blob: 0a9933bf1d376e229ae2692465710bcfc043c1cb [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.
16
17"""
18This script merges two partial target files packages (one of which contains
19system files, and the other contains non-system files) together, producing a
20complete target files package that can be used to generate an OTA package.
21
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
28 --other-target-files other-target-files-zip-archive
29 The input target files package containing other bits. This is a zip
30 archive.
31
32 --output-target-files output-target-files-package
33 The output merged target files package. Also a zip archive.
34"""
35
36from __future__ import print_function
37
Bill Peckhame9eb5f92019-02-01 15:52:10 -080038import fnmatch
39import logging
40import os
41import sys
42import zipfile
43
44import common
45import add_img_to_target_files
46
47logger = logging.getLogger(__name__)
48OPTIONS = common.OPTIONS
49OPTIONS.verbose = True
Bill Peckhamf753e152019-02-19 18:02:46 -080050OPTIONS.system_target_files = None
51OPTIONS.other_target_files = None
52OPTIONS.output_target_files = None
53OPTIONS.keep_tmp = False
Bill Peckhame9eb5f92019-02-01 15:52:10 -080054
55# system_extract_as_is_item_list is a list of items to extract from the partial
56# system target files package as is, meaning these items will land in the
57# output target files package exactly as they appear in the input partial
58# system target files package.
59
60system_extract_as_is_item_list = [
61 'META/apkcerts.txt',
62 'META/filesystem_config.txt',
63 'META/root_filesystem_config.txt',
64 'META/system_manifest.xml',
65 'META/system_matrix.xml',
66 'META/update_engine_config.txt',
67 'PRODUCT/*',
68 'ROOT/*',
69 'SYSTEM/*',
70]
71
72# system_extract_special_item_list is a list of items to extract from the
73# partial system target files package that need some special processing, such
74# as some sort of combination with items from the partial other target files
75# package.
76
77system_extract_special_item_list = [
78 'META/*',
79]
80
81# system_misc_info_keys is a list of keys to obtain from the system instance of
82# META/misc_info.txt. The remaining keys from the other instance.
83
84system_misc_info_keys = [
85 'avb_system_hashtree_enable',
86 'avb_system_add_hashtree_footer_args',
87 'avb_system_key_path',
88 'avb_system_algorithm',
89 'avb_system_rollback_index_location',
90 'avb_product_hashtree_enable',
91 'avb_product_add_hashtree_footer_args',
92 'avb_product_services_hashtree_enable',
93 'avb_product_services_add_hashtree_footer_args',
94 'system_root_image',
95 'root_dir',
96 'ab_update',
97 'default_system_dev_certificate',
98 'system_size',
99]
100
101# other_extract_as_is_item_list is a list of items to extract from the partial
102# other target files package as is, meaning these items will land in the output
103# target files package exactly as they appear in the input partial other target
104# files package.
105
106other_extract_as_is_item_list = [
107 'META/boot_filesystem_config.txt',
108 'META/otakeys.txt',
109 'META/releasetools.py',
110 'META/vendor_filesystem_config.txt',
111 'META/vendor_manifest.xml',
112 'META/vendor_matrix.xml',
113 'BOOT/*',
114 'DATA/*',
115 'ODM/*',
116 'OTA/android-info.txt',
117 'PREBUILT_IMAGES/*',
118 'RADIO/*',
119 'VENDOR/*',
120]
121
122# other_extract_for_merge_item_list is a list of items to extract from the
123# partial other target files package that need some special processing, such as
124# some sort of combination with items from the partial system target files
125# package.
126
127other_extract_special_item_list = [
128 'META/*',
129]
130
131
132def extract_items(target_files, target_files_temp_dir, extract_item_list):
133 """Extract items from target files to temporary directory.
134
135 This function extracts from the specified target files zip archive into the
136 specified temporary directory, the items specified in the extract item list.
137
138 Args:
139 target_files: The target files zip archive from which to extract items.
140
141 target_files_temp_dir: The temporary directory where the extracted items
142 will land.
143
144 extract_item_list: A list of items to extract.
145
146 Returns:
147 On success, 0. Otherwise, a non-zero exit code from unzip.
148 """
149
150 logger.info('extracting from %s', target_files)
151
152 # Filter the extract_item_list to remove any items that do not exist in the
153 # zip file. Otherwise, the extraction step will fail.
154
155 with zipfile.ZipFile(
156 target_files,
157 'r',
158 allowZip64=True) as target_files_zipfile:
159 target_files_namelist = target_files_zipfile.namelist()
160
161 filtered_extract_item_list = []
162 for pattern in extract_item_list:
163 matching_namelist = fnmatch.filter(target_files_namelist, pattern)
164 if not matching_namelist:
165 logger.warning('no match for %s', pattern)
166 else:
167 filtered_extract_item_list.append(pattern)
168
169 # Extract the filtered_extract_item_list from target_files into
170 # target_files_temp_dir.
171
172 # TODO(b/124464492): Extend common.UnzipTemp() to handle this use case.
173 command = [
174 'unzip',
175 '-n',
176 '-q',
177 '-d', target_files_temp_dir,
178 target_files
179 ] + filtered_extract_item_list
180
181 result = common.RunAndWait(command, verbose=True)
182
183 if result != 0:
184 logger.error('extract_items command %s failed %d', str(command), result)
185 return result
186
187 return 0
188
189
190def process_ab_partitions_txt(
191 system_target_files_temp_dir,
192 other_target_files_temp_dir,
193 output_target_files_temp_dir):
194 """Perform special processing for META/ab_partitions.txt
195
196 This function merges the contents of the META/ab_partitions.txt files from
197 the system directory and the other directory, placing the merged result in
198 the output directory. The precondition in that the files are already
199 extracted. The post condition is that the output META/ab_partitions.txt
200 contains the merged content. The format for each ab_partitions.txt a one
201 partition name per line. The output file contains the union of the parition
202 names.
203
204 Args:
205 system_target_files_temp_dir: The name of a directory containing the
206 special items extracted from the system target files package.
207
208 other_target_files_temp_dir: The name of a directory containing the
209 special items extracted from the other target files package.
210
211 output_target_files_temp_dir: The name of a directory that will be used
212 to create the output target files package after all the special cases
213 are processed.
214 """
215
216 system_ab_partitions_txt = os.path.join(
217 system_target_files_temp_dir, 'META', 'ab_partitions.txt')
218
219 other_ab_partitions_txt = os.path.join(
220 other_target_files_temp_dir, 'META', 'ab_partitions.txt')
221
222 with open(system_ab_partitions_txt) as f:
223 system_ab_partitions = f.read().splitlines()
224
225 with open(other_ab_partitions_txt) as f:
226 other_ab_partitions = f.read().splitlines()
227
228 output_ab_partitions = set(system_ab_partitions + other_ab_partitions)
229
230 output_ab_partitions_txt = os.path.join(
231 output_target_files_temp_dir, 'META', 'ab_partitions.txt')
232
233 with open(output_ab_partitions_txt, 'w') as output:
234 for partition in sorted(output_ab_partitions):
235 output.write('%s\n' % partition)
236
237
238def process_misc_info_txt(
239 system_target_files_temp_dir,
240 other_target_files_temp_dir,
241 output_target_files_temp_dir):
242 """Perform special processing for META/misc_info.txt
243
244 This function merges the contents of the META/misc_info.txt files from the
245 system directory and the other directory, placing the merged result in the
246 output directory. The precondition in that the files are already extracted.
247 The post condition is that the output META/misc_info.txt contains the merged
248 content.
249
250 Args:
251 system_target_files_temp_dir: The name of a directory containing the
252 special items extracted from the system target files package.
253
254 other_target_files_temp_dir: The name of a directory containing the
255 special items extracted from the other target files package.
256
257 output_target_files_temp_dir: The name of a directory that will be used
258 to create the output target files package after all the special cases
259 are processed.
260 """
261
262 def read_helper(d):
263 misc_info_txt = os.path.join(d, 'META', 'misc_info.txt')
264 with open(misc_info_txt) as f:
265 return list(f.read().splitlines())
266
267 system_info_dict = common.LoadDictionaryFromLines(
268 read_helper(system_target_files_temp_dir))
269
270 # We take most of the misc info from the other target files.
271
272 merged_info_dict = common.LoadDictionaryFromLines(
273 read_helper(other_target_files_temp_dir))
274
275 # Replace certain values in merged_info_dict with values from
276 # system_info_dict. TODO(b/124467065): This should be more flexible than
277 # using the hard-coded system_misc_info_keys.
278
279 for key in system_misc_info_keys:
280 merged_info_dict[key] = system_info_dict[key]
281
282 output_misc_info_txt = os.path.join(
283 output_target_files_temp_dir,
284 'META', 'misc_info.txt')
285
286 sorted_keys = sorted(merged_info_dict.keys())
287
288 with open(output_misc_info_txt, 'w') as output:
289 for key in sorted_keys:
290 output.write('{}={}\n'.format(key, merged_info_dict[key]))
291
292
293def process_file_contexts_bin(temp_dir, output_target_files_temp_dir):
294 """Perform special processing for META/file_contexts.bin.
295
296 This function combines plat_file_contexts and vendor_file_contexts, which are
297 expected to already be extracted in temp_dir, to produce a merged
298 file_contexts.bin that will land in temp_dir at META/file_contexts.bin.
299
300 Args:
301 temp_dir: The name of a scratch directory that this function can use for
302 intermediate files generated during processing.
303
304 output_target_files_temp_dir: The name of the working directory that must
305 already contain plat_file_contexts and vendor_file_contexts (in the
306 appropriate sub directories), and to which META/file_contexts.bin will be
307 written.
308
309 Returns:
310 On success, 0. Otherwise, a non-zero exit code.
311 """
312
313 # To create a merged file_contexts.bin file, we use the system and vendor
314 # file contexts files as input, the m4 tool to combine them, the sorting tool
315 # to sort, and finally the sefcontext_compile tool to generate the final
316 # output. We currently omit a checkfc step since the files had been checked
317 # as part of the build.
318
319 # The m4 step concatenates the two input files contexts files. Since m4
320 # writes to stdout, we receive that into an array of bytes, and then write it
321 # to a file.
322
323 # Collect the file contexts that we're going to combine from SYSTEM, VENDOR,
324 # PRODUCT, and ODM. We require SYSTEM and VENDOR, but others are optional.
325
326 file_contexts_list = []
327
328 for partition in ['SYSTEM', 'VENDOR', 'PRODUCT', 'ODM']:
329 prefix = 'plat' if partition == 'SYSTEM' else partition.lower()
330
331 file_contexts = os.path.join(
332 output_target_files_temp_dir,
333 partition, 'etc', 'selinux', prefix + '_file_contexts')
334
335 mandatory = partition in ['SYSTEM', 'VENDOR']
336
337 if mandatory or os.path.isfile(file_contexts):
338 file_contexts_list.append(file_contexts)
339 else:
340 logger.warning('file not found: %s', file_contexts)
341
342 command = ['m4', '--fatal-warnings', '-s'] + file_contexts_list
343
344 merged_content = common.RunAndCheckOutput(command, verbose=False)
345
346 merged_file_contexts_txt = os.path.join(temp_dir, 'merged_file_contexts.txt')
347
348 with open(merged_file_contexts_txt, 'wb') as f:
349 f.write(merged_content)
350
351 # The sort step sorts the concatenated file.
352
353 sorted_file_contexts_txt = os.path.join(temp_dir, 'sorted_file_contexts.txt')
354 command = ['fc_sort', merged_file_contexts_txt, sorted_file_contexts_txt]
355
356 # TODO(b/124521133): Refector RunAndWait to raise exception on failure.
357 result = common.RunAndWait(command, verbose=True)
358
359 if result != 0:
360 return result
361
362 # Finally, the compile step creates the final META/file_contexts.bin.
363
364 file_contexts_bin = os.path.join(
365 output_target_files_temp_dir,
366 'META', 'file_contexts.bin')
367
368 command = [
369 'sefcontext_compile',
370 '-o', file_contexts_bin,
371 sorted_file_contexts_txt,
372 ]
373
374 result = common.RunAndWait(command, verbose=True)
375
376 if result != 0:
377 return result
378
379 return 0
380
381
382def process_special_cases(
383 temp_dir,
384 system_target_files_temp_dir,
385 other_target_files_temp_dir,
386 output_target_files_temp_dir):
387 """Perform special-case processing for certain target files items.
388
389 Certain files in the output target files package require special-case
390 processing. This function performs all that special-case processing.
391
392 Args:
393 temp_dir: The name of a scratch directory that this function can use for
394 intermediate files generated during processing.
395
396 system_target_files_temp_dir: The name of a directory containing the
397 special items extracted from the system target files package.
398
399 other_target_files_temp_dir: The name of a directory containing the
400 special items extracted from the other target files package.
401
402 output_target_files_temp_dir: The name of a directory that will be used
403 to create the output target files package after all the special cases
404 are processed.
405
406 Returns:
407 On success, 0. Otherwise, a non-zero exit code.
408 """
409
410 process_ab_partitions_txt(
411 system_target_files_temp_dir=system_target_files_temp_dir,
412 other_target_files_temp_dir=other_target_files_temp_dir,
413 output_target_files_temp_dir=output_target_files_temp_dir)
414
415 process_misc_info_txt(
416 system_target_files_temp_dir=system_target_files_temp_dir,
417 other_target_files_temp_dir=other_target_files_temp_dir,
418 output_target_files_temp_dir=output_target_files_temp_dir)
419
420 result = process_file_contexts_bin(
421 temp_dir=temp_dir,
422 output_target_files_temp_dir=output_target_files_temp_dir)
423
424 if result != 0:
425 return result
426
427 return 0
428
429
430def merge_target_files(
431 temp_dir,
432 system_target_files,
433 other_target_files,
434 output_target_files):
435 """Merge two target files packages together.
436
437 This function takes system and other target files packages as input, performs
438 various file extractions, special case processing, and finally creates a
439 merged zip archive as output.
440
441 Args:
442 temp_dir: The name of a directory we use when we extract items from the
443 input target files packages, and also a scratch directory that we use for
444 temporary files.
445
446 system_target_files: The name of the zip archive containing the system
447 partial target files package.
448
449 other_target_files: The name of the zip archive containing the other
450 partial target files package.
451
452 output_target_files: The name of the output zip archive target files
453 package created by merging system and other.
454
455 Returns:
456 On success, 0. Otherwise, a non-zero exit code.
457 """
458
459 # Create directory names that we'll use when we extract files from system,
460 # and other, and for zipping the final output.
461
462 system_target_files_temp_dir = os.path.join(temp_dir, 'system')
463 other_target_files_temp_dir = os.path.join(temp_dir, 'other')
464 output_target_files_temp_dir = os.path.join(temp_dir, 'output')
465
466 # Extract "as is" items from the input system partial target files package.
467 # We extract them directly into the output temporary directory since the
468 # items do not need special case processing.
469
470 result = extract_items(
471 target_files=system_target_files,
472 target_files_temp_dir=output_target_files_temp_dir,
473 extract_item_list=system_extract_as_is_item_list)
474
475 if result != 0:
476 return result
477
478 # Extract "as is" items from the input other partial target files package. We
479 # extract them directly into the output temporary directory since the items
480 # do not need special case processing.
481
482 result = extract_items(
483 target_files=other_target_files,
484 target_files_temp_dir=output_target_files_temp_dir,
485 extract_item_list=other_extract_as_is_item_list)
486
487 if result != 0:
488 return result
489
490 # Extract "special" items from the input system partial target files package.
491 # We extract these items to different directory since they require special
492 # processing before they will end up in the output directory.
493
494 result = extract_items(
495 target_files=system_target_files,
496 target_files_temp_dir=system_target_files_temp_dir,
497 extract_item_list=system_extract_special_item_list)
498
499 if result != 0:
500 return result
501
502 # Extract "special" items from the input other partial target files package.
503 # We extract these items to different directory since they require special
504 # processing before they will end up in the output directory.
505
506 result = extract_items(
507 target_files=other_target_files,
508 target_files_temp_dir=other_target_files_temp_dir,
509 extract_item_list=other_extract_special_item_list)
510
511 if result != 0:
512 return result
513
514 # Now that the temporary directories contain all the extracted files, perform
515 # special case processing on any items that need it. After this function
516 # completes successfully, all the files we need to create the output target
517 # files package are in place.
518
519 result = process_special_cases(
520 temp_dir=temp_dir,
521 system_target_files_temp_dir=system_target_files_temp_dir,
522 other_target_files_temp_dir=other_target_files_temp_dir,
523 output_target_files_temp_dir=output_target_files_temp_dir)
524
525 if result != 0:
526 return result
527
528 # Regenerate IMAGES in the temporary directory.
529
530 add_img_args = [
531 '--verbose',
532 output_target_files_temp_dir,
533 ]
534
535 add_img_to_target_files.main(add_img_args)
536
537 # Finally, create the output target files zip archive.
538
539 output_zip = os.path.abspath(output_target_files)
540 output_target_files_list = os.path.join(temp_dir, 'output.list')
541 output_target_files_meta_dir = os.path.join(
542 output_target_files_temp_dir, 'META')
543
544 command = [
545 'find',
546 output_target_files_meta_dir,
547 ]
548 # TODO(bpeckham): sort this to be more like build.
549 meta_content = common.RunAndCheckOutput(command, verbose=False)
550 command = [
551 'find',
552 output_target_files_temp_dir,
553 '-path',
554 output_target_files_meta_dir,
555 '-prune',
556 '-o',
557 '-print'
558 ]
559 # TODO(bpeckham): sort this to be more like build.
560 other_content = common.RunAndCheckOutput(command, verbose=False)
561
562 with open(output_target_files_list, 'wb') as f:
563 f.write(meta_content)
564 f.write(other_content)
565
566 command = [
Bill Peckhamf753e152019-02-19 18:02:46 -0800567 'soong_zip',
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800568 '-d',
569 '-o', output_zip,
570 '-C', output_target_files_temp_dir,
571 '-l', output_target_files_list,
572 ]
573 logger.info('creating %s', output_target_files)
574 result = common.RunAndWait(command, verbose=True)
575
576 if result != 0:
577 logger.error('zip command %s failed %d', str(command), result)
578 return result
579
580 return 0
581
582
583def merge_target_files_with_temp_dir(
584 system_target_files,
585 other_target_files,
586 output_target_files,
587 keep_tmp):
588 """Manage the creation and cleanup of the temporary directory.
589
590 This function wraps merge_target_files after first creating a temporary
591 directory. It also cleans up the temporary directory.
592
593 Args:
594 system_target_files: The name of the zip archive containing the system
595 partial target files package.
596
597 other_target_files: The name of the zip archive containing the other
598 partial target files package.
599
600 output_target_files: The name of the output zip archive target files
601 package created by merging system and other.
602
603 keep_tmp: Keep the temporary directory after processing is complete.
604
605 Returns:
606 On success, 0. Otherwise, a non-zero exit code.
607 """
608
609 # Create a temporary directory. This will serve as the parent of directories
610 # we use when we extract items from the input target files packages, and also
611 # a scratch directory that we use for temporary files.
612
613 logger.info(
614 'starting: merge system %s and other %s into output %s',
615 system_target_files,
616 other_target_files,
617 output_target_files)
618
619 temp_dir = common.MakeTempDir(prefix='merge_target_files_')
620
621 try:
622 return merge_target_files(
623 temp_dir=temp_dir,
624 system_target_files=system_target_files,
625 other_target_files=other_target_files,
626 output_target_files=output_target_files)
627 except:
628 raise
629 finally:
630 if keep_tmp:
631 logger.info('keeping %s', temp_dir)
632 else:
633 common.Cleanup()
634
635
636def main():
637 """The main function.
638
639 Process command line arguments, then call merge_target_files_with_temp_dir to
640 perform the heavy lifting.
641
642 Returns:
643 On success, 0. Otherwise, a non-zero exit code.
644 """
645
646 common.InitLogging()
647
Bill Peckhamf753e152019-02-19 18:02:46 -0800648 def option_handler(o, a):
649 if o == '--system-target-files':
650 OPTIONS.system_target_files = a
651 elif o == '--other-target-files':
652 OPTIONS.other_target_files = a
653 elif o == '--output-target-files':
654 OPTIONS.output_target_files = a
655 elif o == '--keep_tmp':
656 OPTIONS.keep_tmp = True
657 else:
658 return False
659 return True
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800660
Bill Peckhamf753e152019-02-19 18:02:46 -0800661 args = common.ParseOptions(
662 sys.argv[1:], __doc__,
663 extra_long_opts=[
664 'system-target-files=',
665 'other-target-files=',
666 'output-target-files=',
667 "keep_tmp",
668 ],
669 extra_option_handler=option_handler)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800670
Bill Peckhamf753e152019-02-19 18:02:46 -0800671 if (len(args) != 0 or
672 OPTIONS.system_target_files is None or
673 OPTIONS.other_target_files is None or
674 OPTIONS.output_target_files is None):
675 common.Usage(__doc__)
676 return 1
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800677
678 return merge_target_files_with_temp_dir(
Bill Peckhamf753e152019-02-19 18:02:46 -0800679 system_target_files=OPTIONS.system_target_files,
680 other_target_files=OPTIONS.other_target_files,
681 output_target_files=OPTIONS.output_target_files,
682 keep_tmp=OPTIONS.keep_tmp)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800683
684
685if __name__ == '__main__':
686 sys.exit(main())