1#!/usr/bin/env python3 2# Copyright 2020 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Utilities for working with ConfigBundle instances more conveniently.""" 6 7import logging 8 9from chromiumos.config.payload.config_bundle_pb2 import ConfigBundle 10from chromiumos.config.payload.flat_config_pb2 import FlatConfigList 11 12from chromiumos.config.api import device_brand_pb2 13from chromiumos.config.api import design_pb2 14from chromiumos.config.api.software import brand_config_pb2 15 16 17def _case_equal(str1, str2): 18 """Case insensitive string equals.""" 19 return str1.lower() == str2.lower() 20 21 22def flatten_config(config: ConfigBundle) -> FlatConfigList: 23 """Take a ConfigBundle and resolve all the values that are referred to by id. 24 25 This denormalizes the ConfigBundle data to make it easier to query. 26 27 Args: 28 config: ConfigBundle instance to denormalize 29 30 Returns 31 FlatConfigList instance containing denormalized data for each device.""" 32 33 # pylint: disable=too-many-locals 34 35 def _lookup(id_value, id_map): 36 """Look up an id value in a given map, raise KeyError if not found""" 37 key = id_value.value 38 if not key: 39 return None 40 41 if key in id_map: 42 return id_map[key] 43 44 raise KeyError('Failed to lookup \'%s\' with value \'%s\'' % 45 (id_value.__class__.__name__.replace('Id', ''), key)) 46 47 # Break out global lists into maps from id => object 48 partners = {p.id.value: p for p in config.partner_list} 49 programs = {p.id.value: p for p in config.program_list} 50 sw_configs = list(config.software_configs) 51 brand_configs = {b.brand_id.value: b for b in config.brand_configs} 52 53 results = FlatConfigList() 54 for hw_design in config.design_list: 55 logging.debug("flattening %s", hw_design.name) 56 57 device_brands = [] 58 if config.device_brand_list: 59 device_brands = [ 60 x for x in config.device_brand_list 61 if x.design_id.value == hw_design.id.value 62 ] 63 64 if not device_brands: 65 device_brands.append(device_brand_pb2.DeviceBrand()) 66 logging.debug( 67 " %d device brands: %s", 68 len(device_brands), 69 device_brands, 70 ) 71 72 for device_brand in device_brands: 73 # Brand config can be empty since platform JSON config allows it 74 brand_config = brand_configs.get(device_brand.id.value, 75 brand_config_pb2.BrandConfig()) 76 77 design_configs = hw_design.configs 78 if not design_configs: 79 design_configs.append(design_pb2.Design.Config()) 80 logging.debug( 81 " %d design configs: %s", 82 len(design_configs), 83 design_configs, 84 ) 85 86 for hw_design_config in design_configs: 87 design_id = hw_design_config.id.value 88 sw_config_matches = [ 89 x for x in sw_configs if x.design_config_id.value == design_id 90 ] 91 flat_config = results.values.add() 92 flat_config.hw_design.MergeFrom(hw_design) 93 flat_config.hw_design_config.MergeFrom(hw_design_config) 94 flat_config.hw_components.MergeFrom(config.components) 95 flat_config.device_brand.MergeFrom(device_brand) 96 flat_config.brand_sw_config.MergeFrom(brand_config) 97 98 if sw_config_matches: 99 flat_config.sw_config.MergeFrom(sw_config_matches[0]) 100 101 # Sometimes programs are a little slow to get properly added, so let's 102 # not fail completely if they're not there, we'll just leave it empty 103 # for now. 104 try: 105 flat_config.program.MergeFrom(_lookup(hw_design.program_id, programs)) 106 except KeyError: 107 logging.warning( 108 "program '%s' doesn't seem to be defined (bad config?)", 109 hw_design.program_id.value) 110 111 # We expect program_id to be defined above, but odm/oem is less consistently set 112 if hw_design.odm_id.value: 113 flat_config.odm.MergeFrom(_lookup(hw_design.odm_id, partners)) 114 115 if device_brand.oem_id.value: 116 flat_config.oem.MergeFrom(_lookup(device_brand.oem_id, partners)) 117 118 logging.debug(" created %d flattened entries", len(results.values)) 119 return results 120 121 122def find_partner(bundle: ConfigBundle, name: str, create=False): 123 """Search for a partner with the given name (case insensitive). 124 125 Args: 126 bundle: config_bundle instance to search 127 name: partner name to search for 128 create: if partner isn't found, return a newly created one 129 130 Returns: 131 existing Partner if found, otherwise a newly created one or None 132 """ 133 134 for partner in bundle.partner_list: 135 if _case_equal(partner.name, name): 136 return partner 137 138 if create: 139 partner = bundle.partner_list.add() 140 partner.id.value = name 141 partner.name = name 142 return partner 143 return None 144 145 146def find_program(bundle: ConfigBundle, name: str, create=False): 147 """Search for a program with the given name (case insensitive). 148 149 Args: 150 bundle: config_bundle instance to search 151 name: program name to search for 152 create: if program isn't found, return a newly created one 153 154 Returns: 155 existing Program if found, otherwise a newly created one or None 156 """ 157 158 for program in bundle.program_list: 159 if _case_equal(program.name, name): 160 return program 161 162 if create: 163 program = bundle.program_list.add() 164 program.id.value = name 165 program.name = name 166 return program 167 return None 168 169 170def find_component(bundle: ConfigBundle, id_value: str, create=False): 171 """Search for a component with the given id. 172 173 Args: 174 bundle: ConfigBundle instance to search 175 id_value: Value for Component.Id.Value to search for 176 create: If true, return newly created component if one isn't found. 177 178 Returns: 179 existing Component if found, otherwise a newly create one, else None 180 """ 181 182 for component in bundle.components: 183 if _case_equal(component.id.value, id_value): 184 return component 185 186 if create: 187 component = bundle.components.add() 188 component.id.value = id_value 189 return component 190 return None 191