blob: e8220610ff81166641ed5a7029d8bcd85400cd51 [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
94# The merge config lists should not attempt to extract items from both
95# builds for any of the following partitions. The partitions in
96# SINGLE_BUILD_PARTITIONS should come entirely from a single build (either
97# framework or vendor, but not both).
98
99_SINGLE_BUILD_PARTITIONS = (
100 'BOOT/',
101 'DATA/',
102 'ODM/',
103 'PRODUCT/',
104 'SYSTEM_EXT/',
105 'RADIO/',
106 'RECOVERY/',
107 'ROOT/',
108 'SYSTEM/',
109 'SYSTEM_OTHER/',
110 'VENDOR/',
111 'VENDOR_DLKM/',
112 'ODM_DLKM/',
113 'SYSTEM_DLKM/',
114)
115
116
117def ValidateConfigLists():
118 """Performs validations on the merge config lists.
119
120 Returns:
121 False if a validation fails, otherwise true.
122 """
123 has_error = False
124
125 # Check that partitions only come from one input.
126 for partition in _SINGLE_BUILD_PARTITIONS:
127 image_path = 'IMAGES/{}.img'.format(partition.lower().replace('/', ''))
128 in_framework = (
129 any(item.startswith(partition) for item in OPTIONS.framework_item_list)
130 or image_path in OPTIONS.framework_item_list)
131 in_vendor = (
132 any(item.startswith(partition) for item in OPTIONS.vendor_item_list) or
133 image_path in OPTIONS.vendor_item_list)
134 if in_framework and in_vendor:
135 logger.error(
136 'Cannot extract items from %s for both the framework and vendor'
137 ' builds. Please ensure only one merge config item list'
138 ' includes %s.', partition, partition)
139 has_error = True
140
141 if any([
142 key in OPTIONS.framework_misc_info_keys
143 for key in ('dynamic_partition_list', 'super_partition_groups')
144 ]):
145 logger.error('Dynamic partition misc info keys should come from '
146 'the vendor instance of META/misc_info.txt.')
147 has_error = True
148
149 return not has_error
150
151
152# In an item list (framework or vendor), we may see entries that select whole
153# partitions. Such an entry might look like this 'SYSTEM/*' (e.g., for the
154# system partition). The following regex matches this and extracts the
155# partition name.
156
157_PARTITION_ITEM_PATTERN = re.compile(r'^([A-Z_]+)/\*$')
158
159
160def ItemListToPartitionSet(item_list):
161 """Converts a target files item list to a partition set.
162
163 The item list contains items that might look like 'SYSTEM/*' or 'VENDOR/*' or
164 'OTA/android-info.txt'. Items that end in '/*' are assumed to match entire
165 directories where 'SYSTEM' or 'VENDOR' is a directory name that identifies the
166 contents of a partition of the same name. Other items in the list, such as the
167 'OTA' example contain metadata. This function iterates such a list, returning
168 a set that contains the partition entries.
169
170 Args:
171 item_list: A list of items in a target files package.
172
173 Returns:
174 A set of partitions extracted from the list of items.
175 """
176
177 partition_set = set()
178
179 for item in item_list:
180 partition_match = _PARTITION_ITEM_PATTERN.search(item.strip())
181 partition_tag = partition_match.group(
182 1).lower() if partition_match else None
183
184 if partition_tag:
185 partition_set.add(partition_tag)
186
187 return partition_set