blob: 5e1094c6ed7e262e60bae8ebc73e38a15e38ad18 [file] [log] [blame]
Daniel Norman2465fc82022-03-02 12:01:20 -08001#!/usr/bin/env python
2#
3# Copyright (C) 2022 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"""Common utility functions shared by merge_* scripts.
18
19Expects items in OPTIONS prepared by merge_target_files.py.
20"""
21
22import fnmatch
23import logging
24import os
25import re
26import shutil
27import zipfile
28
29import common
30
31logger = logging.getLogger(__name__)
32OPTIONS = common.OPTIONS
33
34
35def ExtractItems(input_zip, output_dir, extract_item_list):
36 """Extracts items in extract_item_list from a zip to a dir."""
37
38 # Filter the extract_item_list to remove any items that do not exist in the
39 # zip file. Otherwise, the extraction step will fail.
40
41 with zipfile.ZipFile(input_zip, allowZip64=True) as input_zipfile:
42 input_namelist = input_zipfile.namelist()
43
44 filtered_extract_item_list = []
45 for pattern in extract_item_list:
46 if fnmatch.filter(input_namelist, pattern):
47 filtered_extract_item_list.append(pattern)
48
49 common.UnzipToDir(input_zip, output_dir, filtered_extract_item_list)
50
51
52def CopyItems(from_dir, to_dir, patterns):
53 """Similar to ExtractItems() except uses an input dir instead of zip."""
54 file_paths = []
55 for dirpath, _, filenames in os.walk(from_dir):
56 file_paths.extend(
57 os.path.relpath(path=os.path.join(dirpath, filename), start=from_dir)
58 for filename in filenames)
59
60 filtered_file_paths = set()
61 for pattern in patterns:
62 filtered_file_paths.update(fnmatch.filter(file_paths, pattern))
63
64 for file_path in filtered_file_paths:
65 original_file_path = os.path.join(from_dir, file_path)
66 copied_file_path = os.path.join(to_dir, file_path)
67 copied_file_dir = os.path.dirname(copied_file_path)
68 if not os.path.exists(copied_file_dir):
69 os.makedirs(copied_file_dir)
70 if os.path.islink(original_file_path):
71 os.symlink(os.readlink(original_file_path), copied_file_path)
72 else:
73 shutil.copyfile(original_file_path, copied_file_path)
74
75
76def WriteSortedData(data, path):
77 """Writes the sorted contents of either a list or dict to file.
78
79 This function sorts the contents of the list or dict and then writes the
80 resulting sorted contents to a file specified by path.
81
82 Args:
83 data: The list or dict to sort and write.
84 path: Path to the file to write the sorted values to. The file at path will
85 be overridden if it exists.
86 """
87 with open(path, 'w') as output:
88 for entry in sorted(data):
89 out_str = '{}={}\n'.format(entry, data[entry]) if isinstance(
90 data, dict) else '{}\n'.format(entry)
91 output.write(out_str)
92
93
Daniel Norman2465fc82022-03-02 12:01:20 -080094def ValidateConfigLists():
95 """Performs validations on the merge config lists.
96
97 Returns:
98 False if a validation fails, otherwise true.
99 """
100 has_error = False
101
102 # Check that partitions only come from one input.
Daniel Norman679242b2022-03-18 15:46:27 -0700103 framework_partitions = ItemListToPartitionSet(OPTIONS.framework_item_list)
104 vendor_partitions = ItemListToPartitionSet(OPTIONS.vendor_item_list)
105 from_both = framework_partitions.intersection(vendor_partitions)
106 if from_both:
107 logger.error(
108 'Cannot extract items from the same partition in both the '
109 'framework and vendor builds. Please ensure only one merge config '
110 'item list (or inferred list) includes each partition: %s' %
111 ','.join(from_both))
112 has_error = True
Daniel Norman2465fc82022-03-02 12:01:20 -0800113
114 if any([
115 key in OPTIONS.framework_misc_info_keys
116 for key in ('dynamic_partition_list', 'super_partition_groups')
117 ]):
118 logger.error('Dynamic partition misc info keys should come from '
119 'the vendor instance of META/misc_info.txt.')
120 has_error = True
121
122 return not has_error
123
124
125# In an item list (framework or vendor), we may see entries that select whole
126# partitions. Such an entry might look like this 'SYSTEM/*' (e.g., for the
127# system partition). The following regex matches this and extracts the
128# partition name.
129
Daniel Norman679242b2022-03-18 15:46:27 -0700130_PARTITION_ITEM_PATTERN = re.compile(r'^([A-Z_]+)/.*$')
131_IMAGE_PARTITION_PATTERN = re.compile(r'^IMAGES/(.*)\.img$')
Po Hu81c3f4a2023-03-29 17:49:49 +0800132_PREBUILT_IMAGE_PARTITION_PATTERN = re.compile(r'^PREBUILT_IMAGES/(.*)\.img$')
Daniel Norman2465fc82022-03-02 12:01:20 -0800133
134
135def ItemListToPartitionSet(item_list):
136 """Converts a target files item list to a partition set.
137
138 The item list contains items that might look like 'SYSTEM/*' or 'VENDOR/*' or
139 'OTA/android-info.txt'. Items that end in '/*' are assumed to match entire
140 directories where 'SYSTEM' or 'VENDOR' is a directory name that identifies the
141 contents of a partition of the same name. Other items in the list, such as the
142 'OTA' example contain metadata. This function iterates such a list, returning
143 a set that contains the partition entries.
144
145 Args:
146 item_list: A list of items in a target files package.
147
148 Returns:
149 A set of partitions extracted from the list of items.
150 """
151
152 partition_set = set()
153
154 for item in item_list:
Po Hu81c3f4a2023-03-29 17:49:49 +0800155 for pattern in (_PARTITION_ITEM_PATTERN, _IMAGE_PARTITION_PATTERN, _PREBUILT_IMAGE_PARTITION_PATTERN):
Daniel Norman679242b2022-03-18 15:46:27 -0700156 partition_match = pattern.search(item.strip())
157 if partition_match:
158 partition = partition_match.group(1).lower()
159 # These directories in target-files are not actual partitions.
Po Hu81c3f4a2023-03-29 17:49:49 +0800160 if partition not in ('meta', 'images', 'prebuilt_images'):
Daniel Norman679242b2022-03-18 15:46:27 -0700161 partition_set.add(partition)
Daniel Norman2465fc82022-03-02 12:01:20 -0800162
163 return partition_set
Daniel Norman5f476772022-03-02 15:46:34 -0800164
165
166# Partitions that are grabbed from the framework partial build by default.
167_FRAMEWORK_PARTITIONS = {
Daniel Norman679242b2022-03-18 15:46:27 -0700168 'system', 'product', 'system_ext', 'system_other', 'root', 'system_dlkm',
Po Hu81c3f4a2023-03-29 17:49:49 +0800169 'vbmeta_system', 'pvmfw'
Daniel Norman5f476772022-03-02 15:46:34 -0800170}
171
172
173def InferItemList(input_namelist, framework):
Daniel Norman679242b2022-03-18 15:46:27 -0700174 item_set = set()
Daniel Norman5f476772022-03-02 15:46:34 -0800175
Daniel Norman679242b2022-03-18 15:46:27 -0700176 # Some META items are always grabbed from partial builds directly.
Daniel Norman5f476772022-03-02 15:46:34 -0800177 # Others are combined in merge_meta.py.
178 if framework:
Daniel Norman679242b2022-03-18 15:46:27 -0700179 item_set.update([
Daniel Norman5f476772022-03-02 15:46:34 -0800180 'META/liblz4.so',
181 'META/postinstall_config.txt',
Daniel Norman5f476772022-03-02 15:46:34 -0800182 'META/zucchini_config.txt',
183 ])
184 else: # vendor
Daniel Norman679242b2022-03-18 15:46:27 -0700185 item_set.update([
Daniel Norman5f476772022-03-02 15:46:34 -0800186 'META/kernel_configs.txt',
187 'META/kernel_version.txt',
188 'META/otakeys.txt',
Daniel Norman679242b2022-03-18 15:46:27 -0700189 'META/pack_radioimages.txt',
Daniel Norman5f476772022-03-02 15:46:34 -0800190 'META/releasetools.py',
Daniel Norman5f476772022-03-02 15:46:34 -0800191 ])
192
193 # Grab a set of items for the expected partitions in the partial build.
Daniel Norman679242b2022-03-18 15:46:27 -0700194 seen_partitions = []
195 for namelist in input_namelist:
196 if namelist.endswith('/'):
197 continue
Daniel Norman5f476772022-03-02 15:46:34 -0800198
Daniel Norman679242b2022-03-18 15:46:27 -0700199 partition = namelist.split('/')[0].lower()
200
201 # META items are grabbed above, or merged later.
202 if partition == 'meta':
203 continue
204
Po Hu81c3f4a2023-03-29 17:49:49 +0800205 if partition in ('images', 'prebuilt_images'):
Daniel Norman679242b2022-03-18 15:46:27 -0700206 image_partition, extension = os.path.splitext(os.path.basename(namelist))
207 if image_partition == 'vbmeta':
208 # Always regenerate vbmeta.img since it depends on hash information
209 # from both builds.
210 continue
211 if extension in ('.img', '.map'):
212 # Include image files in IMAGES/* if the partition comes from
213 # the expected set.
214 if (framework and image_partition in _FRAMEWORK_PARTITIONS) or (
215 not framework and image_partition not in _FRAMEWORK_PARTITIONS):
216 item_set.add(namelist)
217 elif not framework:
218 # Include all miscellaneous non-image files in IMAGES/* from
219 # the vendor build.
220 item_set.add(namelist)
221 continue
222
223 # Skip already-visited partitions.
224 if partition in seen_partitions:
225 continue
226 seen_partitions.append(partition)
227
228 if (framework and partition in _FRAMEWORK_PARTITIONS) or (
229 not framework and partition not in _FRAMEWORK_PARTITIONS):
230 fs_config_prefix = '' if partition == 'system' else '%s_' % partition
231 item_set.update([
232 '%s/*' % partition.upper(),
233 'META/%sfilesystem_config.txt' % fs_config_prefix,
234 ])
235
236 return sorted(item_set)
Daniel Norman5f476772022-03-02 15:46:34 -0800237
238
239def InferFrameworkMiscInfoKeys(input_namelist):
240 keys = [
241 'ab_update',
242 'avb_vbmeta_system',
243 'avb_vbmeta_system_algorithm',
244 'avb_vbmeta_system_key_path',
245 'avb_vbmeta_system_rollback_index_location',
246 'default_system_dev_certificate',
247 ]
248
249 for partition in _FRAMEWORK_PARTITIONS:
Daniel Norman679242b2022-03-18 15:46:27 -0700250 for partition_dir in ('%s/' % partition.upper(), 'SYSTEM/%s/' % partition):
251 if partition_dir in input_namelist:
Daniel Norman5f476772022-03-02 15:46:34 -0800252 fs_type_prefix = '' if partition == 'system' else '%s_' % partition
253 keys.extend([
254 'avb_%s_hashtree_enable' % partition,
255 'avb_%s_add_hashtree_footer_args' % partition,
256 '%s_disable_sparse' % partition,
257 'building_%s_image' % partition,
258 '%sfs_type' % fs_type_prefix,
259 ])
260
261 return sorted(keys)