• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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