• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/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
32from ota_utils import ParseUpdateEngineConfig
33
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
56
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
77def MergeMetaFiles(temp_dir, merged_dir):
78  """Merges various files in META/*."""
79
80  framework_meta_dir = os.path.join(temp_dir, 'framework_meta', 'META')
81  merge_utils.CollectTargetFiles(
82      input_zipfile_or_dir=OPTIONS.framework_target_files,
83      output_dir=os.path.dirname(framework_meta_dir),
84      item_list=('META/*',))
85
86  vendor_meta_dir = os.path.join(temp_dir, 'vendor_meta', 'META')
87  merge_utils.CollectTargetFiles(
88      input_zipfile_or_dir=OPTIONS.vendor_target_files,
89      output_dir=os.path.dirname(vendor_meta_dir),
90      item_list=('META/*',))
91
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
127  MergeUpdateEngineConfig(
128      framework_meta_dir,
129      vendor_meta_dir, merged_meta_dir,
130  )
131
132  # 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:
172    if key in OPTIONS.framework_misc_info:
173      merged_dict[key] = OPTIONS.framework_misc_info[key]
174
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
315