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