blob: b2bb020e6f416d43863b6cbb6cce9e0dd69fc187 [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
166 # Extract the filtered_extract_item_list from target_files into
167 # target_files_temp_dir.
168
169 # TODO(b/124464492): Extend common.UnzipTemp() to handle this use case.
170 command = [
171 'unzip',
172 '-n',
173 '-q',
174 '-d', target_files_temp_dir,
175 target_files
176 ] + filtered_extract_item_list
177
Bill Peckham889b0c62019-02-21 18:53:37 -0800178 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800179
180
181def process_ab_partitions_txt(
182 system_target_files_temp_dir,
183 other_target_files_temp_dir,
184 output_target_files_temp_dir):
185 """Perform special processing for META/ab_partitions.txt
186
187 This function merges the contents of the META/ab_partitions.txt files from
188 the system directory and the other directory, placing the merged result in
189 the output directory. The precondition in that the files are already
190 extracted. The post condition is that the output META/ab_partitions.txt
191 contains the merged content. The format for each ab_partitions.txt a one
192 partition name per line. The output file contains the union of the parition
193 names.
194
195 Args:
196 system_target_files_temp_dir: The name of a directory containing the
197 special items extracted from the system target files package.
198
199 other_target_files_temp_dir: The name of a directory containing the
200 special items extracted from the other target files package.
201
202 output_target_files_temp_dir: The name of a directory that will be used
203 to create the output target files package after all the special cases
204 are processed.
205 """
206
207 system_ab_partitions_txt = os.path.join(
208 system_target_files_temp_dir, 'META', 'ab_partitions.txt')
209
210 other_ab_partitions_txt = os.path.join(
211 other_target_files_temp_dir, 'META', 'ab_partitions.txt')
212
213 with open(system_ab_partitions_txt) as f:
214 system_ab_partitions = f.read().splitlines()
215
216 with open(other_ab_partitions_txt) as f:
217 other_ab_partitions = f.read().splitlines()
218
219 output_ab_partitions = set(system_ab_partitions + other_ab_partitions)
220
221 output_ab_partitions_txt = os.path.join(
222 output_target_files_temp_dir, 'META', 'ab_partitions.txt')
223
224 with open(output_ab_partitions_txt, 'w') as output:
225 for partition in sorted(output_ab_partitions):
226 output.write('%s\n' % partition)
227
228
229def process_misc_info_txt(
230 system_target_files_temp_dir,
231 other_target_files_temp_dir,
232 output_target_files_temp_dir):
233 """Perform special processing for META/misc_info.txt
234
235 This function merges the contents of the META/misc_info.txt files from the
236 system directory and the other directory, placing the merged result in the
237 output directory. The precondition in that the files are already extracted.
238 The post condition is that the output META/misc_info.txt contains the merged
239 content.
240
241 Args:
242 system_target_files_temp_dir: The name of a directory containing the
243 special items extracted from the system target files package.
244
245 other_target_files_temp_dir: The name of a directory containing the
246 special items extracted from the other target files package.
247
248 output_target_files_temp_dir: The name of a directory that will be used
249 to create the output target files package after all the special cases
250 are processed.
251 """
252
253 def read_helper(d):
254 misc_info_txt = os.path.join(d, 'META', 'misc_info.txt')
255 with open(misc_info_txt) as f:
256 return list(f.read().splitlines())
257
258 system_info_dict = common.LoadDictionaryFromLines(
259 read_helper(system_target_files_temp_dir))
260
261 # We take most of the misc info from the other target files.
262
263 merged_info_dict = common.LoadDictionaryFromLines(
264 read_helper(other_target_files_temp_dir))
265
266 # Replace certain values in merged_info_dict with values from
267 # system_info_dict. TODO(b/124467065): This should be more flexible than
268 # using the hard-coded system_misc_info_keys.
269
270 for key in system_misc_info_keys:
271 merged_info_dict[key] = system_info_dict[key]
272
273 output_misc_info_txt = os.path.join(
274 output_target_files_temp_dir,
275 'META', 'misc_info.txt')
276
277 sorted_keys = sorted(merged_info_dict.keys())
278
279 with open(output_misc_info_txt, 'w') as output:
280 for key in sorted_keys:
281 output.write('{}={}\n'.format(key, merged_info_dict[key]))
282
283
284def process_file_contexts_bin(temp_dir, output_target_files_temp_dir):
285 """Perform special processing for META/file_contexts.bin.
286
287 This function combines plat_file_contexts and vendor_file_contexts, which are
288 expected to already be extracted in temp_dir, to produce a merged
289 file_contexts.bin that will land in temp_dir at META/file_contexts.bin.
290
291 Args:
292 temp_dir: The name of a scratch directory that this function can use for
293 intermediate files generated during processing.
294
295 output_target_files_temp_dir: The name of the working directory that must
296 already contain plat_file_contexts and vendor_file_contexts (in the
297 appropriate sub directories), and to which META/file_contexts.bin will be
298 written.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800299 """
300
301 # To create a merged file_contexts.bin file, we use the system and vendor
302 # file contexts files as input, the m4 tool to combine them, the sorting tool
303 # to sort, and finally the sefcontext_compile tool to generate the final
304 # output. We currently omit a checkfc step since the files had been checked
305 # as part of the build.
306
307 # The m4 step concatenates the two input files contexts files. Since m4
308 # writes to stdout, we receive that into an array of bytes, and then write it
309 # to a file.
310
311 # Collect the file contexts that we're going to combine from SYSTEM, VENDOR,
312 # PRODUCT, and ODM. We require SYSTEM and VENDOR, but others are optional.
313
314 file_contexts_list = []
315
316 for partition in ['SYSTEM', 'VENDOR', 'PRODUCT', 'ODM']:
317 prefix = 'plat' if partition == 'SYSTEM' else partition.lower()
318
319 file_contexts = os.path.join(
320 output_target_files_temp_dir,
321 partition, 'etc', 'selinux', prefix + '_file_contexts')
322
323 mandatory = partition in ['SYSTEM', 'VENDOR']
324
325 if mandatory or os.path.isfile(file_contexts):
326 file_contexts_list.append(file_contexts)
327 else:
328 logger.warning('file not found: %s', file_contexts)
329
330 command = ['m4', '--fatal-warnings', '-s'] + file_contexts_list
331
332 merged_content = common.RunAndCheckOutput(command, verbose=False)
333
334 merged_file_contexts_txt = os.path.join(temp_dir, 'merged_file_contexts.txt')
335
336 with open(merged_file_contexts_txt, 'wb') as f:
337 f.write(merged_content)
338
339 # The sort step sorts the concatenated file.
340
341 sorted_file_contexts_txt = os.path.join(temp_dir, 'sorted_file_contexts.txt')
342 command = ['fc_sort', merged_file_contexts_txt, sorted_file_contexts_txt]
Bill Peckham889b0c62019-02-21 18:53:37 -0800343 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800344
345 # Finally, the compile step creates the final META/file_contexts.bin.
346
347 file_contexts_bin = os.path.join(
348 output_target_files_temp_dir,
349 'META', 'file_contexts.bin')
350
351 command = [
352 'sefcontext_compile',
353 '-o', file_contexts_bin,
354 sorted_file_contexts_txt,
355 ]
356
Bill Peckham889b0c62019-02-21 18:53:37 -0800357 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800358
359
360def process_special_cases(
361 temp_dir,
362 system_target_files_temp_dir,
363 other_target_files_temp_dir,
364 output_target_files_temp_dir):
365 """Perform special-case processing for certain target files items.
366
367 Certain files in the output target files package require special-case
368 processing. This function performs all that special-case processing.
369
370 Args:
371 temp_dir: The name of a scratch directory that this function can use for
372 intermediate files generated during processing.
373
374 system_target_files_temp_dir: The name of a directory containing the
375 special items extracted from the system target files package.
376
377 other_target_files_temp_dir: The name of a directory containing the
378 special items extracted from the other target files package.
379
380 output_target_files_temp_dir: The name of a directory that will be used
381 to create the output target files package after all the special cases
382 are processed.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800383 """
384
385 process_ab_partitions_txt(
386 system_target_files_temp_dir=system_target_files_temp_dir,
387 other_target_files_temp_dir=other_target_files_temp_dir,
388 output_target_files_temp_dir=output_target_files_temp_dir)
389
390 process_misc_info_txt(
391 system_target_files_temp_dir=system_target_files_temp_dir,
392 other_target_files_temp_dir=other_target_files_temp_dir,
393 output_target_files_temp_dir=output_target_files_temp_dir)
394
Bill Peckham889b0c62019-02-21 18:53:37 -0800395 process_file_contexts_bin(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800396 temp_dir=temp_dir,
397 output_target_files_temp_dir=output_target_files_temp_dir)
398
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800399
400def merge_target_files(
401 temp_dir,
402 system_target_files,
403 other_target_files,
404 output_target_files):
405 """Merge two target files packages together.
406
407 This function takes system and other target files packages as input, performs
408 various file extractions, special case processing, and finally creates a
409 merged zip archive as output.
410
411 Args:
412 temp_dir: The name of a directory we use when we extract items from the
413 input target files packages, and also a scratch directory that we use for
414 temporary files.
415
416 system_target_files: The name of the zip archive containing the system
417 partial target files package.
418
419 other_target_files: The name of the zip archive containing the other
420 partial target files package.
421
422 output_target_files: The name of the output zip archive target files
423 package created by merging system and other.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800424 """
425
426 # Create directory names that we'll use when we extract files from system,
427 # and other, and for zipping the final output.
428
429 system_target_files_temp_dir = os.path.join(temp_dir, 'system')
430 other_target_files_temp_dir = os.path.join(temp_dir, 'other')
431 output_target_files_temp_dir = os.path.join(temp_dir, 'output')
432
433 # Extract "as is" items from the input system partial target files package.
434 # We extract them directly into the output temporary directory since the
435 # items do not need special case processing.
436
Bill Peckham889b0c62019-02-21 18:53:37 -0800437 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800438 target_files=system_target_files,
439 target_files_temp_dir=output_target_files_temp_dir,
440 extract_item_list=system_extract_as_is_item_list)
441
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800442 # Extract "as is" items from the input other partial target files package. We
443 # extract them directly into the output temporary directory since the items
444 # do not need special case processing.
445
Bill Peckham889b0c62019-02-21 18:53:37 -0800446 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800447 target_files=other_target_files,
448 target_files_temp_dir=output_target_files_temp_dir,
449 extract_item_list=other_extract_as_is_item_list)
450
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800451 # Extract "special" items from the input system partial target files package.
452 # We extract these items to different directory since they require special
453 # processing before they will end up in the output directory.
454
Bill Peckham889b0c62019-02-21 18:53:37 -0800455 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800456 target_files=system_target_files,
457 target_files_temp_dir=system_target_files_temp_dir,
458 extract_item_list=system_extract_special_item_list)
459
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800460 # Extract "special" items from the input other partial target files package.
461 # We extract these items to different directory since they require special
462 # processing before they will end up in the output directory.
463
Bill Peckham889b0c62019-02-21 18:53:37 -0800464 extract_items(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800465 target_files=other_target_files,
466 target_files_temp_dir=other_target_files_temp_dir,
467 extract_item_list=other_extract_special_item_list)
468
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800469 # Now that the temporary directories contain all the extracted files, perform
470 # special case processing on any items that need it. After this function
471 # completes successfully, all the files we need to create the output target
472 # files package are in place.
473
Bill Peckham889b0c62019-02-21 18:53:37 -0800474 process_special_cases(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800475 temp_dir=temp_dir,
476 system_target_files_temp_dir=system_target_files_temp_dir,
477 other_target_files_temp_dir=other_target_files_temp_dir,
478 output_target_files_temp_dir=output_target_files_temp_dir)
479
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800480 # Regenerate IMAGES in the temporary directory.
481
482 add_img_args = [
483 '--verbose',
484 output_target_files_temp_dir,
485 ]
486
487 add_img_to_target_files.main(add_img_args)
488
489 # Finally, create the output target files zip archive.
490
491 output_zip = os.path.abspath(output_target_files)
492 output_target_files_list = os.path.join(temp_dir, 'output.list')
493 output_target_files_meta_dir = os.path.join(
494 output_target_files_temp_dir, 'META')
495
496 command = [
497 'find',
498 output_target_files_meta_dir,
499 ]
500 # TODO(bpeckham): sort this to be more like build.
501 meta_content = common.RunAndCheckOutput(command, verbose=False)
502 command = [
503 'find',
504 output_target_files_temp_dir,
505 '-path',
506 output_target_files_meta_dir,
507 '-prune',
508 '-o',
509 '-print'
510 ]
511 # TODO(bpeckham): sort this to be more like build.
512 other_content = common.RunAndCheckOutput(command, verbose=False)
513
514 with open(output_target_files_list, 'wb') as f:
515 f.write(meta_content)
516 f.write(other_content)
517
518 command = [
Bill Peckhamf753e152019-02-19 18:02:46 -0800519 'soong_zip',
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800520 '-d',
521 '-o', output_zip,
522 '-C', output_target_files_temp_dir,
523 '-l', output_target_files_list,
524 ]
525 logger.info('creating %s', output_target_files)
Bill Peckham889b0c62019-02-21 18:53:37 -0800526 common.RunAndWait(command, verbose=True)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800527
528
529def merge_target_files_with_temp_dir(
530 system_target_files,
531 other_target_files,
532 output_target_files,
533 keep_tmp):
534 """Manage the creation and cleanup of the temporary directory.
535
536 This function wraps merge_target_files after first creating a temporary
537 directory. It also cleans up the temporary directory.
538
539 Args:
540 system_target_files: The name of the zip archive containing the system
541 partial target files package.
542
543 other_target_files: The name of the zip archive containing the other
544 partial target files package.
545
546 output_target_files: The name of the output zip archive target files
547 package created by merging system and other.
548
549 keep_tmp: Keep the temporary directory after processing is complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800550 """
551
552 # Create a temporary directory. This will serve as the parent of directories
553 # we use when we extract items from the input target files packages, and also
554 # a scratch directory that we use for temporary files.
555
556 logger.info(
557 'starting: merge system %s and other %s into output %s',
558 system_target_files,
559 other_target_files,
560 output_target_files)
561
562 temp_dir = common.MakeTempDir(prefix='merge_target_files_')
563
564 try:
Bill Peckham889b0c62019-02-21 18:53:37 -0800565 merge_target_files(
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800566 temp_dir=temp_dir,
567 system_target_files=system_target_files,
568 other_target_files=other_target_files,
569 output_target_files=output_target_files)
570 except:
571 raise
572 finally:
573 if keep_tmp:
574 logger.info('keeping %s', temp_dir)
575 else:
576 common.Cleanup()
577
578
579def main():
580 """The main function.
581
582 Process command line arguments, then call merge_target_files_with_temp_dir to
583 perform the heavy lifting.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800584 """
585
586 common.InitLogging()
587
Bill Peckhamf753e152019-02-19 18:02:46 -0800588 def option_handler(o, a):
589 if o == '--system-target-files':
590 OPTIONS.system_target_files = a
591 elif o == '--other-target-files':
592 OPTIONS.other_target_files = a
593 elif o == '--output-target-files':
594 OPTIONS.output_target_files = a
595 elif o == '--keep_tmp':
596 OPTIONS.keep_tmp = True
597 else:
598 return False
599 return True
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800600
Bill Peckhamf753e152019-02-19 18:02:46 -0800601 args = common.ParseOptions(
602 sys.argv[1:], __doc__,
603 extra_long_opts=[
604 'system-target-files=',
605 'other-target-files=',
606 'output-target-files=',
607 "keep_tmp",
608 ],
609 extra_option_handler=option_handler)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800610
Bill Peckham889b0c62019-02-21 18:53:37 -0800611 if (len(args) != 0 or
Bill Peckhamf753e152019-02-19 18:02:46 -0800612 OPTIONS.system_target_files is None or
613 OPTIONS.other_target_files is None or
614 OPTIONS.output_target_files is None):
615 common.Usage(__doc__)
Bill Peckham889b0c62019-02-21 18:53:37 -0800616 sys.exit(1)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800617
Bill Peckham889b0c62019-02-21 18:53:37 -0800618 merge_target_files_with_temp_dir(
Bill Peckhamf753e152019-02-19 18:02:46 -0800619 system_target_files=OPTIONS.system_target_files,
620 other_target_files=OPTIONS.other_target_files,
621 output_target_files=OPTIONS.output_target_files,
622 keep_tmp=OPTIONS.keep_tmp)
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800623
624
625if __name__ == '__main__':
Bill Peckham889b0c62019-02-21 18:53:37 -0800626 main()