blob: 2645829dfe964f84db0cd6519991f5331f342768 [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.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800145 """
146
147 logger.info('extracting from %s', target_files)
148
149 # Filter the extract_item_list to remove any items that do not exist in the
150 # zip file. Otherwise, the extraction step will fail.
151
152 with zipfile.ZipFile(
153 target_files,
154 'r',
155 allowZip64=True) as target_files_zipfile:
156 target_files_namelist = target_files_zipfile.namelist()
157
158 filtered_extract_item_list = []
159 for pattern in extract_item_list:
160 matching_namelist = fnmatch.filter(target_files_namelist, pattern)
161 if not matching_namelist:
162 logger.warning('no match for %s', pattern)
163 else:
164 filtered_extract_item_list.append(pattern)
165
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800166 # Extract from target_files into target_files_temp_dir the
167 # filtered_extract_item_list.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800168
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800169 common.UnzipToDir(
170 target_files,
171 target_files_temp_dir,
172 filtered_extract_item_list)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800173
174
175def process_ab_partitions_txt(
176 system_target_files_temp_dir,
177 other_target_files_temp_dir,
178 output_target_files_temp_dir):
179 """Perform special processing for META/ab_partitions.txt
180
181 This function merges the contents of the META/ab_partitions.txt files from
182 the system directory and the other directory, placing the merged result in
183 the output directory. The precondition in that the files are already
184 extracted. The post condition is that the output META/ab_partitions.txt
185 contains the merged content. The format for each ab_partitions.txt a one
186 partition name per line. The output file contains the union of the parition
187 names.
188
189 Args:
190 system_target_files_temp_dir: The name of a directory containing the
191 special items extracted from the system target files package.
192
193 other_target_files_temp_dir: The name of a directory containing the
194 special items extracted from the other target files package.
195
196 output_target_files_temp_dir: The name of a directory that will be used
197 to create the output target files package after all the special cases
198 are processed.
199 """
200
201 system_ab_partitions_txt = os.path.join(
202 system_target_files_temp_dir, 'META', 'ab_partitions.txt')
203
204 other_ab_partitions_txt = os.path.join(
205 other_target_files_temp_dir, 'META', 'ab_partitions.txt')
206
207 with open(system_ab_partitions_txt) as f:
208 system_ab_partitions = f.read().splitlines()
209
210 with open(other_ab_partitions_txt) as f:
211 other_ab_partitions = f.read().splitlines()
212
213 output_ab_partitions = set(system_ab_partitions + other_ab_partitions)
214
215 output_ab_partitions_txt = os.path.join(
216 output_target_files_temp_dir, 'META', 'ab_partitions.txt')
217
218 with open(output_ab_partitions_txt, 'w') as output:
219 for partition in sorted(output_ab_partitions):
220 output.write('%s\n' % partition)
221
222
223def process_misc_info_txt(
224 system_target_files_temp_dir,
225 other_target_files_temp_dir,
226 output_target_files_temp_dir):
227 """Perform special processing for META/misc_info.txt
228
229 This function merges the contents of the META/misc_info.txt files from the
230 system directory and the other directory, placing the merged result in the
231 output directory. The precondition in that the files are already extracted.
232 The post condition is that the output META/misc_info.txt contains the merged
233 content.
234
235 Args:
236 system_target_files_temp_dir: The name of a directory containing the
237 special items extracted from the system target files package.
238
239 other_target_files_temp_dir: The name of a directory containing the
240 special items extracted from the other target files package.
241
242 output_target_files_temp_dir: The name of a directory that will be used
243 to create the output target files package after all the special cases
244 are processed.
245 """
246
247 def read_helper(d):
248 misc_info_txt = os.path.join(d, 'META', 'misc_info.txt')
249 with open(misc_info_txt) as f:
250 return list(f.read().splitlines())
251
252 system_info_dict = common.LoadDictionaryFromLines(
253 read_helper(system_target_files_temp_dir))
254
255 # We take most of the misc info from the other target files.
256
257 merged_info_dict = common.LoadDictionaryFromLines(
258 read_helper(other_target_files_temp_dir))
259
260 # Replace certain values in merged_info_dict with values from
261 # system_info_dict. TODO(b/124467065): This should be more flexible than
262 # using the hard-coded system_misc_info_keys.
263
264 for key in system_misc_info_keys:
265 merged_info_dict[key] = system_info_dict[key]
266
267 output_misc_info_txt = os.path.join(
268 output_target_files_temp_dir,
269 'META', 'misc_info.txt')
270
271 sorted_keys = sorted(merged_info_dict.keys())
272
273 with open(output_misc_info_txt, 'w') as output:
274 for key in sorted_keys:
275 output.write('{}={}\n'.format(key, merged_info_dict[key]))
276
277
278def process_file_contexts_bin(temp_dir, output_target_files_temp_dir):
279 """Perform special processing for META/file_contexts.bin.
280
281 This function combines plat_file_contexts and vendor_file_contexts, which are
282 expected to already be extracted in temp_dir, to produce a merged
283 file_contexts.bin that will land in temp_dir at META/file_contexts.bin.
284
285 Args:
286 temp_dir: The name of a scratch directory that this function can use for
287 intermediate files generated during processing.
288
289 output_target_files_temp_dir: The name of the working directory that must
290 already contain plat_file_contexts and vendor_file_contexts (in the
291 appropriate sub directories), and to which META/file_contexts.bin will be
292 written.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800293 """
294
295 # To create a merged file_contexts.bin file, we use the system and vendor
296 # file contexts files as input, the m4 tool to combine them, the sorting tool
297 # to sort, and finally the sefcontext_compile tool to generate the final
298 # output. We currently omit a checkfc step since the files had been checked
299 # as part of the build.
300
301 # The m4 step concatenates the two input files contexts files. Since m4
302 # writes to stdout, we receive that into an array of bytes, and then write it
303 # to a file.
304
305 # Collect the file contexts that we're going to combine from SYSTEM, VENDOR,
306 # PRODUCT, and ODM. We require SYSTEM and VENDOR, but others are optional.
307
308 file_contexts_list = []
309
310 for partition in ['SYSTEM', 'VENDOR', 'PRODUCT', 'ODM']:
311 prefix = 'plat' if partition == 'SYSTEM' else partition.lower()
312
313 file_contexts = os.path.join(
314 output_target_files_temp_dir,
315 partition, 'etc', 'selinux', prefix + '_file_contexts')
316
317 mandatory = partition in ['SYSTEM', 'VENDOR']
318
319 if mandatory or os.path.isfile(file_contexts):
320 file_contexts_list.append(file_contexts)
321 else:
322 logger.warning('file not found: %s', file_contexts)
323
324 command = ['m4', '--fatal-warnings', '-s'] + file_contexts_list
325
326 merged_content = common.RunAndCheckOutput(command, verbose=False)
327
328 merged_file_contexts_txt = os.path.join(temp_dir, 'merged_file_contexts.txt')
329
330 with open(merged_file_contexts_txt, 'wb') as f:
331 f.write(merged_content)
332
333 # The sort step sorts the concatenated file.
334
335 sorted_file_contexts_txt = os.path.join(temp_dir, 'sorted_file_contexts.txt')
336 command = ['fc_sort', merged_file_contexts_txt, sorted_file_contexts_txt]
Bill Peckham889b0c62019-02-21 18:53:37 -0800337 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800338
339 # Finally, the compile step creates the final META/file_contexts.bin.
340
341 file_contexts_bin = os.path.join(
342 output_target_files_temp_dir,
343 'META', 'file_contexts.bin')
344
345 command = [
346 'sefcontext_compile',
347 '-o', file_contexts_bin,
348 sorted_file_contexts_txt,
349 ]
350
Bill Peckham889b0c62019-02-21 18:53:37 -0800351 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800352
353
354def process_special_cases(
355 temp_dir,
356 system_target_files_temp_dir,
357 other_target_files_temp_dir,
358 output_target_files_temp_dir):
359 """Perform special-case processing for certain target files items.
360
361 Certain files in the output target files package require special-case
362 processing. This function performs all that special-case processing.
363
364 Args:
365 temp_dir: The name of a scratch directory that this function can use for
366 intermediate files generated during processing.
367
368 system_target_files_temp_dir: The name of a directory containing the
369 special items extracted from the system target files package.
370
371 other_target_files_temp_dir: The name of a directory containing the
372 special items extracted from the other target files package.
373
374 output_target_files_temp_dir: The name of a directory that will be used
375 to create the output target files package after all the special cases
376 are processed.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800377 """
378
379 process_ab_partitions_txt(
380 system_target_files_temp_dir=system_target_files_temp_dir,
381 other_target_files_temp_dir=other_target_files_temp_dir,
382 output_target_files_temp_dir=output_target_files_temp_dir)
383
384 process_misc_info_txt(
385 system_target_files_temp_dir=system_target_files_temp_dir,
386 other_target_files_temp_dir=other_target_files_temp_dir,
387 output_target_files_temp_dir=output_target_files_temp_dir)
388
Bill Peckham889b0c62019-02-21 18:53:37 -0800389 process_file_contexts_bin(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800390 temp_dir=temp_dir,
391 output_target_files_temp_dir=output_target_files_temp_dir)
392
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800393
394def merge_target_files(
395 temp_dir,
396 system_target_files,
397 other_target_files,
398 output_target_files):
399 """Merge two target files packages together.
400
401 This function takes system and other target files packages as input, performs
402 various file extractions, special case processing, and finally creates a
403 merged zip archive as output.
404
405 Args:
406 temp_dir: The name of a directory we use when we extract items from the
407 input target files packages, and also a scratch directory that we use for
408 temporary files.
409
410 system_target_files: The name of the zip archive containing the system
411 partial target files package.
412
413 other_target_files: The name of the zip archive containing the other
414 partial target files package.
415
416 output_target_files: The name of the output zip archive target files
417 package created by merging system and other.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800418 """
419
420 # Create directory names that we'll use when we extract files from system,
421 # and other, and for zipping the final output.
422
423 system_target_files_temp_dir = os.path.join(temp_dir, 'system')
424 other_target_files_temp_dir = os.path.join(temp_dir, 'other')
425 output_target_files_temp_dir = os.path.join(temp_dir, 'output')
426
427 # Extract "as is" items from the input system partial target files package.
428 # We extract them directly into the output temporary directory since the
429 # items do not need special case processing.
430
Bill Peckham889b0c62019-02-21 18:53:37 -0800431 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800432 target_files=system_target_files,
433 target_files_temp_dir=output_target_files_temp_dir,
434 extract_item_list=system_extract_as_is_item_list)
435
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800436 # Extract "as is" items from the input other partial target files package. We
437 # extract them directly into the output temporary directory since the items
438 # do not need special case processing.
439
Bill Peckham889b0c62019-02-21 18:53:37 -0800440 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800441 target_files=other_target_files,
442 target_files_temp_dir=output_target_files_temp_dir,
443 extract_item_list=other_extract_as_is_item_list)
444
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800445 # Extract "special" items from the input system partial target files package.
446 # We extract these items to different directory since they require special
447 # processing before they will end up in the output directory.
448
Bill Peckham889b0c62019-02-21 18:53:37 -0800449 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800450 target_files=system_target_files,
451 target_files_temp_dir=system_target_files_temp_dir,
452 extract_item_list=system_extract_special_item_list)
453
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800454 # Extract "special" items from the input other partial target files package.
455 # We extract these items to different directory since they require special
456 # processing before they will end up in the output directory.
457
Bill Peckham889b0c62019-02-21 18:53:37 -0800458 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800459 target_files=other_target_files,
460 target_files_temp_dir=other_target_files_temp_dir,
461 extract_item_list=other_extract_special_item_list)
462
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800463 # Now that the temporary directories contain all the extracted files, perform
464 # special case processing on any items that need it. After this function
465 # completes successfully, all the files we need to create the output target
466 # files package are in place.
467
Bill Peckham889b0c62019-02-21 18:53:37 -0800468 process_special_cases(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800469 temp_dir=temp_dir,
470 system_target_files_temp_dir=system_target_files_temp_dir,
471 other_target_files_temp_dir=other_target_files_temp_dir,
472 output_target_files_temp_dir=output_target_files_temp_dir)
473
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800474 # Regenerate IMAGES in the temporary directory.
475
476 add_img_args = [
477 '--verbose',
478 output_target_files_temp_dir,
479 ]
480
481 add_img_to_target_files.main(add_img_args)
482
483 # Finally, create the output target files zip archive.
484
485 output_zip = os.path.abspath(output_target_files)
486 output_target_files_list = os.path.join(temp_dir, 'output.list')
487 output_target_files_meta_dir = os.path.join(
488 output_target_files_temp_dir, 'META')
489
490 command = [
491 'find',
492 output_target_files_meta_dir,
493 ]
494 # TODO(bpeckham): sort this to be more like build.
495 meta_content = common.RunAndCheckOutput(command, verbose=False)
496 command = [
497 'find',
498 output_target_files_temp_dir,
499 '-path',
500 output_target_files_meta_dir,
501 '-prune',
502 '-o',
503 '-print'
504 ]
505 # TODO(bpeckham): sort this to be more like build.
506 other_content = common.RunAndCheckOutput(command, verbose=False)
507
508 with open(output_target_files_list, 'wb') as f:
509 f.write(meta_content)
510 f.write(other_content)
511
512 command = [
Bill Peckhamf753e152019-02-19 18:02:46 -0800513 'soong_zip',
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800514 '-d',
515 '-o', output_zip,
516 '-C', output_target_files_temp_dir,
517 '-l', output_target_files_list,
518 ]
519 logger.info('creating %s', output_target_files)
Bill Peckham889b0c62019-02-21 18:53:37 -0800520 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800521
522
523def merge_target_files_with_temp_dir(
524 system_target_files,
525 other_target_files,
526 output_target_files,
527 keep_tmp):
528 """Manage the creation and cleanup of the temporary directory.
529
530 This function wraps merge_target_files after first creating a temporary
531 directory. It also cleans up the temporary directory.
532
533 Args:
534 system_target_files: The name of the zip archive containing the system
535 partial target files package.
536
537 other_target_files: The name of the zip archive containing the other
538 partial target files package.
539
540 output_target_files: The name of the output zip archive target files
541 package created by merging system and other.
542
543 keep_tmp: Keep the temporary directory after processing is complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800544 """
545
546 # Create a temporary directory. This will serve as the parent of directories
547 # we use when we extract items from the input target files packages, and also
548 # a scratch directory that we use for temporary files.
549
550 logger.info(
551 'starting: merge system %s and other %s into output %s',
552 system_target_files,
553 other_target_files,
554 output_target_files)
555
556 temp_dir = common.MakeTempDir(prefix='merge_target_files_')
557
558 try:
Bill Peckham889b0c62019-02-21 18:53:37 -0800559 merge_target_files(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800560 temp_dir=temp_dir,
561 system_target_files=system_target_files,
562 other_target_files=other_target_files,
563 output_target_files=output_target_files)
564 except:
565 raise
566 finally:
567 if keep_tmp:
568 logger.info('keeping %s', temp_dir)
569 else:
570 common.Cleanup()
571
572
573def main():
574 """The main function.
575
576 Process command line arguments, then call merge_target_files_with_temp_dir to
577 perform the heavy lifting.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800578 """
579
580 common.InitLogging()
581
Bill Peckhamf753e152019-02-19 18:02:46 -0800582 def option_handler(o, a):
583 if o == '--system-target-files':
584 OPTIONS.system_target_files = a
585 elif o == '--other-target-files':
586 OPTIONS.other_target_files = a
587 elif o == '--output-target-files':
588 OPTIONS.output_target_files = a
589 elif o == '--keep_tmp':
590 OPTIONS.keep_tmp = True
591 else:
592 return False
593 return True
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800594
Bill Peckhamf753e152019-02-19 18:02:46 -0800595 args = common.ParseOptions(
596 sys.argv[1:], __doc__,
597 extra_long_opts=[
598 'system-target-files=',
599 'other-target-files=',
600 'output-target-files=',
601 "keep_tmp",
602 ],
603 extra_option_handler=option_handler)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800604
Bill Peckham889b0c62019-02-21 18:53:37 -0800605 if (len(args) != 0 or
Bill Peckhamf753e152019-02-19 18:02:46 -0800606 OPTIONS.system_target_files is None or
607 OPTIONS.other_target_files is None or
608 OPTIONS.output_target_files is None):
609 common.Usage(__doc__)
Bill Peckham889b0c62019-02-21 18:53:37 -0800610 sys.exit(1)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800611
Bill Peckham889b0c62019-02-21 18:53:37 -0800612 merge_target_files_with_temp_dir(
Bill Peckhamf753e152019-02-19 18:02:46 -0800613 system_target_files=OPTIONS.system_target_files,
614 other_target_files=OPTIONS.other_target_files,
615 output_target_files=OPTIONS.output_target_files,
616 keep_tmp=OPTIONS.keep_tmp)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800617
618
619if __name__ == '__main__':
Bill Peckham889b0c62019-02-21 18:53:37 -0800620 main()