blob: 198c973456d5eb9755200280904dd7c092727826 [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"""Functions for merging META/* files from partial builds.
18
19Expects items in OPTIONS prepared by merge_target_files.py.
20"""
21
22import logging
23import os
24import re
25import shutil
26
27import build_image
28import common
29import merge_utils
30import sparse_img
31import verity_utils
Kelvin Zhangfcd731e2023-04-04 10:28:11 -070032from ota_utils import ParseUpdateEngineConfig
Daniel Norman2465fc82022-03-02 12:01:20 -080033
34from common import ExternalError
35
36logger = logging.getLogger(__name__)
37
38OPTIONS = common.OPTIONS
39
40# In apexkeys.txt or apkcerts.txt, we will find partition tags on each entry in
41# the file. We use these partition tags to filter the entries in those files
42# from the two different target files packages to produce a merged apexkeys.txt
43# or apkcerts.txt file. A partition tag (e.g., for the product partition) looks
44# like this: 'partition="product"'. We use the group syntax grab the value of
45# the tag. We use non-greedy matching in case there are other fields on the
46# same line.
47
48PARTITION_TAG_PATTERN = re.compile(r'partition="(.*?)"')
49
50# The sorting algorithm for apexkeys.txt and apkcerts.txt does not include the
51# ".apex" or ".apk" suffix, so we use the following pattern to extract a key.
52
53MODULE_KEY_PATTERN = re.compile(r'name="(.+)\.(apex|apk)"')
54
55
Kelvin Zhangfa40c042022-11-09 10:59:25 -080056def MergeUpdateEngineConfig(input_metadir1, input_metadir2, merged_meta_dir):
57 UPDATE_ENGINE_CONFIG_NAME = "update_engine_config.txt"
58 config1_path = os.path.join(
59 input_metadir1, UPDATE_ENGINE_CONFIG_NAME)
60 config2_path = os.path.join(
61 input_metadir2, UPDATE_ENGINE_CONFIG_NAME)
62 config1 = ParseUpdateEngineConfig(config1_path)
63 config2 = ParseUpdateEngineConfig(config2_path)
64 # Copy older config to merged target files for maximum compatibility
65 # update_engine in system partition is from system side, but
66 # update_engine_sideload in recovery is from vendor side.
67 if config1 < config2:
68 shutil.copy(config1_path, os.path.join(
69 merged_meta_dir, UPDATE_ENGINE_CONFIG_NAME))
70 else:
71 shutil.copy(config2_path, os.path.join(
72 merged_meta_dir, UPDATE_ENGINE_CONFIG_NAME))
73
74
Dennis Song36ce3262023-09-13 06:53:00 +000075def MergeMetaFiles(temp_dir, merged_dir, framework_partitions):
Daniel Norman2465fc82022-03-02 12:01:20 -080076 """Merges various files in META/*."""
77
78 framework_meta_dir = os.path.join(temp_dir, 'framework_meta', 'META')
Dennis Song5bfa43e2023-03-30 18:28:00 +080079 merge_utils.CollectTargetFiles(
80 input_zipfile_or_dir=OPTIONS.framework_target_files,
Daniel Norman2465fc82022-03-02 12:01:20 -080081 output_dir=os.path.dirname(framework_meta_dir),
Dennis Song5bfa43e2023-03-30 18:28:00 +080082 item_list=('META/*',))
Daniel Norman2465fc82022-03-02 12:01:20 -080083
84 vendor_meta_dir = os.path.join(temp_dir, 'vendor_meta', 'META')
Dennis Song5bfa43e2023-03-30 18:28:00 +080085 merge_utils.CollectTargetFiles(
86 input_zipfile_or_dir=OPTIONS.vendor_target_files,
Daniel Norman2465fc82022-03-02 12:01:20 -080087 output_dir=os.path.dirname(vendor_meta_dir),
Dennis Song5bfa43e2023-03-30 18:28:00 +080088 item_list=('META/*',))
Daniel Norman2465fc82022-03-02 12:01:20 -080089
90 merged_meta_dir = os.path.join(merged_dir, 'META')
91
92 # Merge META/misc_info.txt into OPTIONS.merged_misc_info,
93 # but do not write it yet. The following functions may further
94 # modify this dict.
95 OPTIONS.merged_misc_info = MergeMiscInfo(
96 framework_meta_dir=framework_meta_dir,
97 vendor_meta_dir=vendor_meta_dir,
98 merged_meta_dir=merged_meta_dir)
99
100 CopyNamedFileContexts(
101 framework_meta_dir=framework_meta_dir,
102 vendor_meta_dir=vendor_meta_dir,
103 merged_meta_dir=merged_meta_dir)
104
105 if OPTIONS.merged_misc_info.get('use_dynamic_partitions') == 'true':
106 MergeDynamicPartitionsInfo(
107 framework_meta_dir=framework_meta_dir,
108 vendor_meta_dir=vendor_meta_dir,
109 merged_meta_dir=merged_meta_dir)
110
111 if OPTIONS.merged_misc_info.get('ab_update') == 'true':
112 MergeAbPartitions(
113 framework_meta_dir=framework_meta_dir,
114 vendor_meta_dir=vendor_meta_dir,
Dennis Song36ce3262023-09-13 06:53:00 +0000115 merged_meta_dir=merged_meta_dir,
116 framework_partitions=framework_partitions)
Daniel Norman2465fc82022-03-02 12:01:20 -0800117 UpdateCareMapImageSizeProps(images_dir=os.path.join(merged_dir, 'IMAGES'))
118
119 for file_name in ('apkcerts.txt', 'apexkeys.txt'):
120 MergePackageKeys(
121 framework_meta_dir=framework_meta_dir,
122 vendor_meta_dir=vendor_meta_dir,
123 merged_meta_dir=merged_meta_dir,
124 file_name=file_name)
125
Himanshu Jakhmola21ef2c62023-07-12 08:11:12 +0530126 if OPTIONS.merged_misc_info.get('ab_update') == 'true':
127 MergeUpdateEngineConfig(
128 framework_meta_dir,
129 vendor_meta_dir, merged_meta_dir)
Kelvin Zhangfa40c042022-11-09 10:59:25 -0800130
Daniel Norman2465fc82022-03-02 12:01:20 -0800131 # Write the now-finalized OPTIONS.merged_misc_info.
132 merge_utils.WriteSortedData(
133 data=OPTIONS.merged_misc_info,
134 path=os.path.join(merged_meta_dir, 'misc_info.txt'))
135
136
Dennis Song36ce3262023-09-13 06:53:00 +0000137def MergeAbPartitions(framework_meta_dir, vendor_meta_dir, merged_meta_dir,
138 framework_partitions):
Daniel Norman2465fc82022-03-02 12:01:20 -0800139 """Merges META/ab_partitions.txt.
140
141 The output contains the union of the partition names.
142 """
143 with open(os.path.join(framework_meta_dir, 'ab_partitions.txt')) as f:
Dennis Song36ce3262023-09-13 06:53:00 +0000144 # Filter out some partitions here to support the case that the
145 # ab_partitions.txt of framework-target-files has non-framework partitions.
146 # This case happens when we use a complete merged target files package as
147 # the framework-target-files.
148 framework_ab_partitions = [
149 partition
150 for partition in f.read().splitlines()
151 if partition in framework_partitions
152 ]
Daniel Norman2465fc82022-03-02 12:01:20 -0800153
154 with open(os.path.join(vendor_meta_dir, 'ab_partitions.txt')) as f:
155 vendor_ab_partitions = f.read().splitlines()
156
157 merge_utils.WriteSortedData(
158 data=set(framework_ab_partitions + vendor_ab_partitions),
159 path=os.path.join(merged_meta_dir, 'ab_partitions.txt'))
160
161
162def MergeMiscInfo(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
163 """Merges META/misc_info.txt.
164
165 The output contains a combination of key=value pairs from both inputs.
166 Most pairs are taken from the vendor input, while some are taken from
167 the framework input.
168 """
169
170 OPTIONS.framework_misc_info = common.LoadDictionaryFromFile(
171 os.path.join(framework_meta_dir, 'misc_info.txt'))
172 OPTIONS.vendor_misc_info = common.LoadDictionaryFromFile(
173 os.path.join(vendor_meta_dir, 'misc_info.txt'))
174
175 # Merged misc info is a combination of vendor misc info plus certain values
176 # from the framework misc info.
177
178 merged_dict = OPTIONS.vendor_misc_info
179 for key in OPTIONS.framework_misc_info_keys:
Daniel Norman5f476772022-03-02 15:46:34 -0800180 if key in OPTIONS.framework_misc_info:
181 merged_dict[key] = OPTIONS.framework_misc_info[key]
Daniel Norman2465fc82022-03-02 12:01:20 -0800182
183 # If AVB is enabled then ensure that we build vbmeta.img.
184 # Partial builds with AVB enabled may set PRODUCT_BUILD_VBMETA_IMAGE=false to
185 # skip building an incomplete vbmeta.img.
186 if merged_dict.get('avb_enable') == 'true':
187 merged_dict['avb_building_vbmeta_image'] = 'true'
188
189 return merged_dict
190
191
192def MergeDynamicPartitionsInfo(framework_meta_dir, vendor_meta_dir,
193 merged_meta_dir):
194 """Merge META/dynamic_partitions_info.txt."""
195 framework_dynamic_partitions_dict = common.LoadDictionaryFromFile(
196 os.path.join(framework_meta_dir, 'dynamic_partitions_info.txt'))
197 vendor_dynamic_partitions_dict = common.LoadDictionaryFromFile(
198 os.path.join(vendor_meta_dir, 'dynamic_partitions_info.txt'))
199
200 merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
201 framework_dict=framework_dynamic_partitions_dict,
202 vendor_dict=vendor_dynamic_partitions_dict)
203
204 merge_utils.WriteSortedData(
205 data=merged_dynamic_partitions_dict,
206 path=os.path.join(merged_meta_dir, 'dynamic_partitions_info.txt'))
207
208 # Merge misc info keys used for Dynamic Partitions.
209 OPTIONS.merged_misc_info.update(merged_dynamic_partitions_dict)
210 # Ensure that add_img_to_target_files rebuilds super split images for
211 # devices that retrofit dynamic partitions. This flag may have been set to
212 # false in the partial builds to prevent duplicate building of super.img.
213 OPTIONS.merged_misc_info['build_super_partition'] = 'true'
214
215
216def MergePackageKeys(framework_meta_dir, vendor_meta_dir, merged_meta_dir,
217 file_name):
218 """Merges APK/APEX key list files."""
219
220 if file_name not in ('apkcerts.txt', 'apexkeys.txt'):
221 raise ExternalError(
222 'Unexpected file_name provided to merge_package_keys_txt: %s',
223 file_name)
224
225 def read_helper(d):
226 temp = {}
227 with open(os.path.join(d, file_name)) as f:
228 for line in f.read().splitlines():
229 line = line.strip()
230 if line:
231 name_search = MODULE_KEY_PATTERN.search(line.split()[0])
232 temp[name_search.group(1)] = line
233 return temp
234
235 framework_dict = read_helper(framework_meta_dir)
236 vendor_dict = read_helper(vendor_meta_dir)
237 merged_dict = {}
238
239 def filter_into_merged_dict(item_dict, partition_set):
240 for key, value in item_dict.items():
241 tag_search = PARTITION_TAG_PATTERN.search(value)
242
243 if tag_search is None:
244 raise ValueError('Entry missing partition tag: %s' % value)
245
246 partition_tag = tag_search.group(1)
247
248 if partition_tag in partition_set:
249 if key in merged_dict:
250 if OPTIONS.allow_duplicate_apkapex_keys:
251 # TODO(b/150582573) Always raise on duplicates.
252 logger.warning('Duplicate key %s' % key)
253 continue
254 else:
255 raise ValueError('Duplicate key %s' % key)
256
257 merged_dict[key] = value
258
259 # Prioritize framework keys first.
260 # Duplicate keys from vendor are an error, or ignored.
261 filter_into_merged_dict(framework_dict, OPTIONS.framework_partition_set)
262 filter_into_merged_dict(vendor_dict, OPTIONS.vendor_partition_set)
263
264 # The following code is similar to WriteSortedData, but different enough
265 # that we couldn't use that function. We need the output to be sorted by the
266 # basename of the apex/apk (without the ".apex" or ".apk" suffix). This
267 # allows the sort to be consistent with the framework/vendor input data and
268 # eases comparison of input data with merged data.
269 with open(os.path.join(merged_meta_dir, file_name), 'w') as output:
270 for key, value in sorted(merged_dict.items()):
271 output.write(value + '\n')
272
273
274def CopyNamedFileContexts(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
275 """Creates named copies of each partial build's file_contexts.bin.
276
277 Used when regenerating images from the partial build.
278 """
279
280 def copy_fc_file(source_dir, file_name):
281 for name in (file_name, 'file_contexts.bin'):
282 fc_path = os.path.join(source_dir, name)
283 if os.path.exists(fc_path):
284 shutil.copyfile(fc_path, os.path.join(merged_meta_dir, file_name))
285 return
286 raise ValueError('Missing file_contexts file from %s: %s', source_dir,
287 file_name)
288
289 copy_fc_file(framework_meta_dir, 'framework_file_contexts.bin')
290 copy_fc_file(vendor_meta_dir, 'vendor_file_contexts.bin')
291
292 # Replace <image>_selinux_fc values with framework or vendor file_contexts.bin
293 # depending on which dictionary the key came from.
294 # Only the file basename is required because all selinux_fc properties are
295 # replaced with the full path to the file under META/ when misc_info.txt is
296 # loaded from target files for repacking. See common.py LoadInfoDict().
297 for key in OPTIONS.vendor_misc_info:
298 if key.endswith('_selinux_fc'):
299 OPTIONS.merged_misc_info[key] = 'vendor_file_contexts.bin'
300 for key in OPTIONS.framework_misc_info:
301 if key.endswith('_selinux_fc'):
302 OPTIONS.merged_misc_info[key] = 'framework_file_contexts.bin'
303
304
305def UpdateCareMapImageSizeProps(images_dir):
306 """Sets <partition>_image_size props in misc_info.
307
308 add_images_to_target_files uses these props to generate META/care_map.pb.
309 Regenerated images will have this property set during regeneration.
310
311 However, images copied directly from input partial target files packages
312 need this value calculated here.
313 """
314 for partition in common.PARTITIONS_WITH_CARE_MAP:
315 image_path = os.path.join(images_dir, '{}.img'.format(partition))
316 if os.path.exists(image_path):
317 partition_size = sparse_img.GetImagePartitionSize(image_path)
318 image_props = build_image.ImagePropFromGlobalDict(
319 OPTIONS.merged_misc_info, partition)
320 verity_image_builder = verity_utils.CreateVerityImageBuilder(image_props)
321 image_size = verity_image_builder.CalculateMaxImageSize(partition_size)
322 OPTIONS.merged_misc_info['{}_image_size'.format(partition)] = image_size