blob: 81f672956d1b4d42616c4cd0bec50c73ccec40e6 [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
32
33from common import ExternalError
34
35logger = logging.getLogger(__name__)
36
37OPTIONS = common.OPTIONS
38
39# In apexkeys.txt or apkcerts.txt, we will find partition tags on each entry in
40# the file. We use these partition tags to filter the entries in those files
41# from the two different target files packages to produce a merged apexkeys.txt
42# or apkcerts.txt file. A partition tag (e.g., for the product partition) looks
43# like this: 'partition="product"'. We use the group syntax grab the value of
44# the tag. We use non-greedy matching in case there are other fields on the
45# same line.
46
47PARTITION_TAG_PATTERN = re.compile(r'partition="(.*?)"')
48
49# The sorting algorithm for apexkeys.txt and apkcerts.txt does not include the
50# ".apex" or ".apk" suffix, so we use the following pattern to extract a key.
51
52MODULE_KEY_PATTERN = re.compile(r'name="(.+)\.(apex|apk)"')
53
54
55def MergeMetaFiles(temp_dir, merged_dir):
56 """Merges various files in META/*."""
57
58 framework_meta_dir = os.path.join(temp_dir, 'framework_meta', 'META')
59 merge_utils.ExtractItems(
60 input_zip=OPTIONS.framework_target_files,
61 output_dir=os.path.dirname(framework_meta_dir),
62 extract_item_list=('META/*',))
63
64 vendor_meta_dir = os.path.join(temp_dir, 'vendor_meta', 'META')
65 merge_utils.ExtractItems(
66 input_zip=OPTIONS.vendor_target_files,
67 output_dir=os.path.dirname(vendor_meta_dir),
68 extract_item_list=('META/*',))
69
70 merged_meta_dir = os.path.join(merged_dir, 'META')
71
72 # Merge META/misc_info.txt into OPTIONS.merged_misc_info,
73 # but do not write it yet. The following functions may further
74 # modify this dict.
75 OPTIONS.merged_misc_info = MergeMiscInfo(
76 framework_meta_dir=framework_meta_dir,
77 vendor_meta_dir=vendor_meta_dir,
78 merged_meta_dir=merged_meta_dir)
79
80 CopyNamedFileContexts(
81 framework_meta_dir=framework_meta_dir,
82 vendor_meta_dir=vendor_meta_dir,
83 merged_meta_dir=merged_meta_dir)
84
85 if OPTIONS.merged_misc_info.get('use_dynamic_partitions') == 'true':
86 MergeDynamicPartitionsInfo(
87 framework_meta_dir=framework_meta_dir,
88 vendor_meta_dir=vendor_meta_dir,
89 merged_meta_dir=merged_meta_dir)
90
91 if OPTIONS.merged_misc_info.get('ab_update') == 'true':
92 MergeAbPartitions(
93 framework_meta_dir=framework_meta_dir,
94 vendor_meta_dir=vendor_meta_dir,
95 merged_meta_dir=merged_meta_dir)
96 UpdateCareMapImageSizeProps(images_dir=os.path.join(merged_dir, 'IMAGES'))
97
98 for file_name in ('apkcerts.txt', 'apexkeys.txt'):
99 MergePackageKeys(
100 framework_meta_dir=framework_meta_dir,
101 vendor_meta_dir=vendor_meta_dir,
102 merged_meta_dir=merged_meta_dir,
103 file_name=file_name)
104
105 # Write the now-finalized OPTIONS.merged_misc_info.
106 merge_utils.WriteSortedData(
107 data=OPTIONS.merged_misc_info,
108 path=os.path.join(merged_meta_dir, 'misc_info.txt'))
109
110
111def MergeAbPartitions(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
112 """Merges META/ab_partitions.txt.
113
114 The output contains the union of the partition names.
115 """
116 with open(os.path.join(framework_meta_dir, 'ab_partitions.txt')) as f:
117 framework_ab_partitions = f.read().splitlines()
118
119 with open(os.path.join(vendor_meta_dir, 'ab_partitions.txt')) as f:
120 vendor_ab_partitions = f.read().splitlines()
121
122 merge_utils.WriteSortedData(
123 data=set(framework_ab_partitions + vendor_ab_partitions),
124 path=os.path.join(merged_meta_dir, 'ab_partitions.txt'))
125
126
127def MergeMiscInfo(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
128 """Merges META/misc_info.txt.
129
130 The output contains a combination of key=value pairs from both inputs.
131 Most pairs are taken from the vendor input, while some are taken from
132 the framework input.
133 """
134
135 OPTIONS.framework_misc_info = common.LoadDictionaryFromFile(
136 os.path.join(framework_meta_dir, 'misc_info.txt'))
137 OPTIONS.vendor_misc_info = common.LoadDictionaryFromFile(
138 os.path.join(vendor_meta_dir, 'misc_info.txt'))
139
140 # Merged misc info is a combination of vendor misc info plus certain values
141 # from the framework misc info.
142
143 merged_dict = OPTIONS.vendor_misc_info
144 for key in OPTIONS.framework_misc_info_keys:
145 merged_dict[key] = OPTIONS.framework_misc_info[key]
146
147 # If AVB is enabled then ensure that we build vbmeta.img.
148 # Partial builds with AVB enabled may set PRODUCT_BUILD_VBMETA_IMAGE=false to
149 # skip building an incomplete vbmeta.img.
150 if merged_dict.get('avb_enable') == 'true':
151 merged_dict['avb_building_vbmeta_image'] = 'true'
152
153 return merged_dict
154
155
156def MergeDynamicPartitionsInfo(framework_meta_dir, vendor_meta_dir,
157 merged_meta_dir):
158 """Merge META/dynamic_partitions_info.txt."""
159 framework_dynamic_partitions_dict = common.LoadDictionaryFromFile(
160 os.path.join(framework_meta_dir, 'dynamic_partitions_info.txt'))
161 vendor_dynamic_partitions_dict = common.LoadDictionaryFromFile(
162 os.path.join(vendor_meta_dir, 'dynamic_partitions_info.txt'))
163
164 merged_dynamic_partitions_dict = common.MergeDynamicPartitionInfoDicts(
165 framework_dict=framework_dynamic_partitions_dict,
166 vendor_dict=vendor_dynamic_partitions_dict)
167
168 merge_utils.WriteSortedData(
169 data=merged_dynamic_partitions_dict,
170 path=os.path.join(merged_meta_dir, 'dynamic_partitions_info.txt'))
171
172 # Merge misc info keys used for Dynamic Partitions.
173 OPTIONS.merged_misc_info.update(merged_dynamic_partitions_dict)
174 # Ensure that add_img_to_target_files rebuilds super split images for
175 # devices that retrofit dynamic partitions. This flag may have been set to
176 # false in the partial builds to prevent duplicate building of super.img.
177 OPTIONS.merged_misc_info['build_super_partition'] = 'true'
178
179
180def MergePackageKeys(framework_meta_dir, vendor_meta_dir, merged_meta_dir,
181 file_name):
182 """Merges APK/APEX key list files."""
183
184 if file_name not in ('apkcerts.txt', 'apexkeys.txt'):
185 raise ExternalError(
186 'Unexpected file_name provided to merge_package_keys_txt: %s',
187 file_name)
188
189 def read_helper(d):
190 temp = {}
191 with open(os.path.join(d, file_name)) as f:
192 for line in f.read().splitlines():
193 line = line.strip()
194 if line:
195 name_search = MODULE_KEY_PATTERN.search(line.split()[0])
196 temp[name_search.group(1)] = line
197 return temp
198
199 framework_dict = read_helper(framework_meta_dir)
200 vendor_dict = read_helper(vendor_meta_dir)
201 merged_dict = {}
202
203 def filter_into_merged_dict(item_dict, partition_set):
204 for key, value in item_dict.items():
205 tag_search = PARTITION_TAG_PATTERN.search(value)
206
207 if tag_search is None:
208 raise ValueError('Entry missing partition tag: %s' % value)
209
210 partition_tag = tag_search.group(1)
211
212 if partition_tag in partition_set:
213 if key in merged_dict:
214 if OPTIONS.allow_duplicate_apkapex_keys:
215 # TODO(b/150582573) Always raise on duplicates.
216 logger.warning('Duplicate key %s' % key)
217 continue
218 else:
219 raise ValueError('Duplicate key %s' % key)
220
221 merged_dict[key] = value
222
223 # Prioritize framework keys first.
224 # Duplicate keys from vendor are an error, or ignored.
225 filter_into_merged_dict(framework_dict, OPTIONS.framework_partition_set)
226 filter_into_merged_dict(vendor_dict, OPTIONS.vendor_partition_set)
227
228 # The following code is similar to WriteSortedData, but different enough
229 # that we couldn't use that function. We need the output to be sorted by the
230 # basename of the apex/apk (without the ".apex" or ".apk" suffix). This
231 # allows the sort to be consistent with the framework/vendor input data and
232 # eases comparison of input data with merged data.
233 with open(os.path.join(merged_meta_dir, file_name), 'w') as output:
234 for key, value in sorted(merged_dict.items()):
235 output.write(value + '\n')
236
237
238def CopyNamedFileContexts(framework_meta_dir, vendor_meta_dir, merged_meta_dir):
239 """Creates named copies of each partial build's file_contexts.bin.
240
241 Used when regenerating images from the partial build.
242 """
243
244 def copy_fc_file(source_dir, file_name):
245 for name in (file_name, 'file_contexts.bin'):
246 fc_path = os.path.join(source_dir, name)
247 if os.path.exists(fc_path):
248 shutil.copyfile(fc_path, os.path.join(merged_meta_dir, file_name))
249 return
250 raise ValueError('Missing file_contexts file from %s: %s', source_dir,
251 file_name)
252
253 copy_fc_file(framework_meta_dir, 'framework_file_contexts.bin')
254 copy_fc_file(vendor_meta_dir, 'vendor_file_contexts.bin')
255
256 # Replace <image>_selinux_fc values with framework or vendor file_contexts.bin
257 # depending on which dictionary the key came from.
258 # Only the file basename is required because all selinux_fc properties are
259 # replaced with the full path to the file under META/ when misc_info.txt is
260 # loaded from target files for repacking. See common.py LoadInfoDict().
261 for key in OPTIONS.vendor_misc_info:
262 if key.endswith('_selinux_fc'):
263 OPTIONS.merged_misc_info[key] = 'vendor_file_contexts.bin'
264 for key in OPTIONS.framework_misc_info:
265 if key.endswith('_selinux_fc'):
266 OPTIONS.merged_misc_info[key] = 'framework_file_contexts.bin'
267
268
269def UpdateCareMapImageSizeProps(images_dir):
270 """Sets <partition>_image_size props in misc_info.
271
272 add_images_to_target_files uses these props to generate META/care_map.pb.
273 Regenerated images will have this property set during regeneration.
274
275 However, images copied directly from input partial target files packages
276 need this value calculated here.
277 """
278 for partition in common.PARTITIONS_WITH_CARE_MAP:
279 image_path = os.path.join(images_dir, '{}.img'.format(partition))
280 if os.path.exists(image_path):
281 partition_size = sparse_img.GetImagePartitionSize(image_path)
282 image_props = build_image.ImagePropFromGlobalDict(
283 OPTIONS.merged_misc_info, partition)
284 verity_image_builder = verity_utils.CreateVerityImageBuilder(image_props)
285 image_size = verity_image_builder.CalculateMaxImageSize(partition_size)
286 OPTIONS.merged_misc_info['{}_image_size'.format(partition)] = image_size