Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright 2020 The Chromium OS Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | # This file uses 2-space indentations. |
| 7 | # pylint: disable=bad-indentation |
| 8 | |
| 9 | # This is contrib-quality code: not all functions/classes are |
| 10 | # documented. |
| 11 | # pylint: disable=missing-function-docstring |
| 12 | # pylint: disable=missing-class-docstring |
| 13 | # pylint: disable=class-missing-docstring |
| 14 | |
| 15 | # Classes make heavy-use of setattr to dynamically set the attributes |
| 16 | # on an object. Disable this check which gets confused very |
| 17 | # frequently. |
| 18 | # pylint: disable=no-member |
| 19 | |
| 20 | """Utility script to auto-convert a pre-unibuild board to unibuild.""" |
| 21 | |
| 22 | import argparse |
| 23 | import datetime |
| 24 | import json |
| 25 | import os |
| 26 | import pathlib |
| 27 | import re |
| 28 | import shlex |
| 29 | import subprocess |
| 30 | import sys |
| 31 | import tempfile |
| 32 | # pylint: disable=import-error |
| 33 | import yaml |
| 34 | # pylint: enable=import-error |
| 35 | |
| 36 | |
| 37 | make_defaults_search_and_destroy_re = re.compile( |
| 38 | r'(?:^\s*)*(?:^\s*#.*\s*)*^\s*USE="\s*\$\{?USE\}?\s*-unibuild\s*"\s*$', |
| 39 | re.MULTILINE) |
| 40 | |
| 41 | |
| 42 | def log(message): |
| 43 | print('[{}] {}'.format(datetime.datetime.now(), message), file=sys.stderr) |
| 44 | |
| 45 | |
| 46 | def prepend_all_lines(text, prepend): |
| 47 | return ''.join( |
| 48 | '{}{}\n'.format(prepend, line) |
| 49 | for line in text.splitlines()) |
| 50 | |
| 51 | |
| 52 | def gen_cros_copyright(line_comment='# '): |
| 53 | return prepend_all_lines( |
| 54 | """Copyright {} The Chromium OS Authors. All rights reserved. |
Jack Rosenthal | f72933c | 2020-06-24 13:37:50 -0600 | [diff] [blame] | 55 | Use of this source code is governed by a BSD-style license that can be |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 56 | found in the LICENSE file.""".format(datetime.datetime.now().strftime('%Y')), |
| 57 | line_comment) |
| 58 | |
| 59 | |
| 60 | def yaml_str_representer(dumper, data): |
| 61 | style = None |
| 62 | tag = 'tag:yaml.org,2002:str' |
| 63 | if '\n' in data: |
| 64 | style = '|' |
| 65 | return dumper.represent_scalar(tag, data, style) |
| 66 | |
| 67 | |
| 68 | yaml.add_representer(str, yaml_str_representer) |
| 69 | |
| 70 | |
| 71 | def format_yaml(config): |
| 72 | conf_str = yaml.dump(config, indent=2, default_flow_style=False) |
| 73 | out = gen_cros_copyright() |
| 74 | out += """ |
| 75 | # This board only supports a single config, defined below, as it is a |
| 76 | # migrated pre-unibuild device. |
| 77 | device-config: &device_config\n""" |
| 78 | out += prepend_all_lines(conf_str, ' ') |
| 79 | out += """ |
| 80 | # Required dunder for chromeos-config to support a single device. |
| 81 | chromeos: |
| 82 | devices: |
| 83 | - skus: |
| 84 | - config: *device_config\n""" |
| 85 | return out |
| 86 | |
| 87 | |
| 88 | def generate_vpackage(depends): |
| 89 | return gen_cros_copyright() + """ |
| 90 | EAPI=7 |
| 91 | |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 92 | DESCRIPTION="ChromeOS Unibuild Config virtual package" |
| 93 | HOMEPAGE="https://chromium.googlesource.com/chromiumos/platform2/+/master/chromeos-config/README.md" |
| 94 | |
| 95 | LICENSE="BSD-Google" |
| 96 | SLOT="0" |
Jack Rosenthal | be2f127 | 2020-06-10 09:31:09 -0600 | [diff] [blame] | 97 | KEYWORDS="*" |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 98 | |
| 99 | DEPEND="%(depends)s" |
| 100 | RDEPEND="${DEPEND}" |
| 101 | """ % { |
| 102 | 'depends': |
| 103 | (''.join('\n\t{}'.format(d) for d in depends) + '\n') |
| 104 | if len(depends) > 1 else |
| 105 | ''.join(depends)} |
| 106 | |
| 107 | |
| 108 | def generate_bsp_ebuild(private=False): |
| 109 | return gen_cros_copyright() + """ |
| 110 | EAPI=7 |
| 111 | |
| 112 | # cros_workon applies only to ebuild and files directory. Use the |
| 113 | # canonical empty project. |
| 114 | CROS_WORKON_PROJECT="chromiumos/infra/build/empty-project" |
Jack Rosenthal | be2f127 | 2020-06-10 09:31:09 -0600 | [diff] [blame] | 115 | CROS_WORKON_LOCALNAME="platform/empty-project" |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 116 | |
| 117 | inherit cros-workon cros-unibuild |
| 118 | |
| 119 | DESCRIPTION="ChromeOS model configuration" |
| 120 | HOMEPAGE="https://chromium.googlesource.com/chromiumos/platform2/+/master/chromeos-config/README.md" |
| 121 | |
| 122 | LICENSE="BSD-Google" |
| 123 | SLOT="0" |
| 124 | KEYWORDS="~*" |
| 125 | |
| 126 | src_install() { |
| 127 | \tinstall%(maybe_private)s_model_files |
| 128 | } |
| 129 | """ % {'maybe_private': '_private' if private else ''} |
| 130 | |
| 131 | |
| 132 | def generate_firmware_ebuild(board_name): |
| 133 | return gen_cros_copyright() + """ |
| 134 | # Change this version number when any change is made to model.yaml |
| 135 | # in order to trigger an auto-revbump is required. |
| 136 | # VERSION=REVBUMP-0.0.1 |
| 137 | |
| 138 | EAPI=7 |
| 139 | CROS_WORKON_COMMIT="" |
| 140 | CROS_WORKON_TREE="" |
| 141 | CROS_WORKON_LOCALNAME="platform/firmware" |
| 142 | CROS_WORKON_PROJECT="chromiumos/platform/firmware" |
| 143 | CROS_BOARDS=( %(board_name)s ) |
| 144 | |
| 145 | inherit cros-workon cros-firmware cros-unibuild |
| 146 | |
| 147 | DESCRIPTION="Chrome OS Firmware (%(board_name)s)" |
| 148 | HOMEPAGE="http://src.chromium.org" |
| 149 | LICENSE="BSD-Google" |
| 150 | SLOT="0" |
| 151 | KEYWORDS="~*" |
| 152 | |
| 153 | RDEPEND="" |
| 154 | |
| 155 | # Unified Builds firmware URL's are read from: |
| 156 | # chromeos-base/chromeos-config-bsp-private/files/model.yaml |
| 157 | # in this repository. Those config files output the SRC_URI's used by Portage. |
| 158 | # |
| 159 | # Update the model.yaml, then run this command from the |
| 160 | # src/platform/dev/contrib directory: |
| 161 | # |
| 162 | # ./cros_update_firmware --board=%(board_name)s |
| 163 | # |
| 164 | # Verify the changes by running: |
| 165 | # /build/%(board_name)s/usr/sbin/chromeos-firmwareupdate --manifest |
| 166 | # |
| 167 | # If this works then you can create a CL with your changes, which should include |
| 168 | # the files: |
| 169 | # chromeos-base/chromeos-config-bsp-private/files/model.yaml |
| 170 | # chromeos-base/chromeos-firmware-%(board_name)s/Manifest |
| 171 | # chromeos-base/chromeos-firmware-%(board_name)s/files/srcuris |
| 172 | # chromeos-base/chromeos-firmware-%(board_name)s/chromeos-firmware-%(board_name)s-9999.ebuild |
| 173 | cros-firmware_setup_source |
| 174 | """ % {'board_name': board_name} |
| 175 | |
| 176 | |
| 177 | def find_file(searchdir, name): |
| 178 | results = [] |
| 179 | for root, _, files in os.walk(searchdir): |
| 180 | if name in files: |
| 181 | results.append(pathlib.Path(root) / name) |
| 182 | return results |
| 183 | |
| 184 | |
| 185 | def find_one_file(searchdir, name): |
| 186 | results = find_file(searchdir, name) |
| 187 | assert len(results) == 1 |
| 188 | return results.pop() |
| 189 | |
| 190 | |
| 191 | def sh_getvar(script, varname): |
| 192 | script = script + ('\necho "${%s}"\n' % varname) |
| 193 | with tempfile.NamedTemporaryFile('w') as f: |
| 194 | f.write(script) |
| 195 | f.flush() |
| 196 | res = subprocess.run(['sh', f.name], stdout=subprocess.PIPE, |
| 197 | check=True, encoding='utf-8') |
| 198 | return res.stdout.strip() or None |
| 199 | |
| 200 | |
Jack Rosenthal | be2f127 | 2020-06-10 09:31:09 -0600 | [diff] [blame] | 201 | def write_file(fullpath, file_contents, make_ebuild_symlink=False): |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 202 | os.makedirs(fullpath.parent, exist_ok=True) |
| 203 | log('Writing {}...'.format(fullpath)) |
| 204 | with open(fullpath, 'w') as f: |
| 205 | f.write(file_contents) |
Jack Rosenthal | be2f127 | 2020-06-10 09:31:09 -0600 | [diff] [blame] | 206 | if make_ebuild_symlink: |
| 207 | if not fullpath.name.endswith('.ebuild'): |
| 208 | raise ValueError( |
| 209 | 'make_ebuild_symlink specified, but path does not look like an ebuild') |
| 210 | prefix, _, _ = fullpath.name.rpartition('.') |
| 211 | linkname = fullpath.parent / '{}-r1.ebuild'.format(prefix) |
| 212 | log('Creating symlink {} -> {}...'.format(linkname, fullpath)) |
| 213 | os.symlink(fullpath.name, linkname) |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 214 | |
| 215 | |
| 216 | def generate_make_defaults(contents): |
| 217 | contents = make_defaults_search_and_destroy_re.sub('', contents) |
| 218 | contents += """ |
| 219 | # Enable chromeos-config. |
| 220 | USE="${USE} unibuild" |
| 221 | """ |
| 222 | return contents |
| 223 | |
| 224 | |
| 225 | class CrosConfig: |
| 226 | def __init__(self, public_yaml_raw, private_yaml_raw): |
| 227 | with tempfile.NamedTemporaryFile(mode='w', delete=False) as merged_tempfile, \ |
| 228 | tempfile.NamedTemporaryFile(mode='w') as public_yaml_tempfile, \ |
| 229 | tempfile.NamedTemporaryFile(mode='w') as private_yaml_tempfile: |
| 230 | public_yaml_tempfile.write(public_yaml_raw) |
| 231 | public_yaml_tempfile.flush() |
| 232 | |
| 233 | private_yaml_tempfile.write(private_yaml_raw) |
| 234 | private_yaml_tempfile.flush() |
| 235 | |
| 236 | log('Merging and validating config schema...') |
| 237 | subprocess.run(['cros_config_schema', '-o', merged_tempfile.name, |
| 238 | '-m', public_yaml_tempfile.name, |
| 239 | private_yaml_tempfile.name], check=True) |
| 240 | self.merged_yaml = merged_tempfile.name |
| 241 | |
| 242 | def run_host_command(self, *args): |
| 243 | return subprocess.run(['cros_config_host', '-c', self.merged_yaml] |
| 244 | + list(args), |
| 245 | check=True, encoding='utf-8', |
| 246 | stdout=subprocess.PIPE).stdout |
| 247 | |
| 248 | |
| 249 | class BoardOverlays: |
| 250 | FIRMWARE_ATTRS = [ |
| 251 | ('CROS_FIRMWARE_MAIN_IMAGE', 'bcs_main_ro'), |
| 252 | ('CROS_FIRMWARE_MAIN_RW_IMAGE', 'bcs_main_rw'), |
| 253 | ('CROS_FIRMWARE_EC_IMAGE', 'bcs_ec'), |
| 254 | ('CROS_FIRMWARE_PD_IMAGE', 'bcs_pd'), |
| 255 | ] |
| 256 | |
| 257 | MAKE_DEFAULTS_ATTRS = [ |
| 258 | ('EC_FIRMWARE', 'ec_firmwares'), |
| 259 | ('PD_FIRMWARE', 'pd_firmwares'), |
| 260 | ('EC_FIRMWARE_EXTRA', 'ec_firmware_extras'), |
| 261 | ('FPMCU_FIRMWARE', 'fpmcu_firmware'), |
| 262 | ('USE', 'use_flags'), |
| 263 | ] |
| 264 | |
| 265 | def __init__(self, board_name, checkout, mosys_platform): |
Jack Rosenthal | 19b40eb | 2020-06-24 15:38:45 -0600 | [diff] [blame] | 266 | self.checkout = checkout |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 267 | self.board_name = board_name |
| 268 | self.mosys_platform = mosys_platform |
| 269 | self.public_overlay = (checkout / 'src' / 'overlays' |
| 270 | / f'overlay-{board_name}') |
| 271 | log('Public overlay path: {}'.format(self.public_overlay)) |
| 272 | self.private_overlay = (checkout / 'src' / 'private-overlays' |
| 273 | / f'overlay-{board_name}-private') |
| 274 | log('Private overlay path: {}'.format(self.private_overlay)) |
| 275 | |
| 276 | assert self.public_overlay.is_dir() |
| 277 | assert self.private_overlay.is_dir() |
| 278 | |
| 279 | # Find the firmware ebuild |
| 280 | self.firmware_ebuild_path = find_one_file( |
| 281 | self.private_overlay, f'chromeos-firmware-{board_name}-9999.ebuild') |
| 282 | log('Firmware ebuild path: {}'.format(self.firmware_ebuild_path)) |
| 283 | |
| 284 | # Read the firmware attrs from it |
| 285 | for _, attr in self.FIRMWARE_ATTRS: |
| 286 | setattr(self, attr, None) |
| 287 | |
| 288 | with open(self.firmware_ebuild_path) as f: |
| 289 | for line in f: |
| 290 | if '#' in line: |
| 291 | line, _, _ = line.partition('#') |
| 292 | line = line.strip() |
| 293 | |
| 294 | for var, attr in self.FIRMWARE_ATTRS: |
| 295 | if line.startswith('{}='.format(var)): |
| 296 | _, _, value = line.partition('=') |
| 297 | value = value.replace('"', '').replace("'", '') |
| 298 | setattr(self, attr, value) |
| 299 | |
| 300 | # Find make.defaults files |
| 301 | self.public_make_defaults_file = ( |
| 302 | self.public_overlay / 'profiles' / 'base' / 'make.defaults') |
| 303 | self.private_make_defaults_file = ( |
| 304 | self.private_overlay / 'profiles' / 'base' / 'make.defaults') |
| 305 | |
| 306 | with open(self.public_make_defaults_file) as f: |
| 307 | self.public_make_defaults = f.read() |
| 308 | with open(self.private_make_defaults_file) as f: |
| 309 | self.private_make_defaults = f.read() |
| 310 | |
| 311 | for var, attr in self.MAKE_DEFAULTS_ATTRS: |
| 312 | setattr(self, attr, set()) |
| 313 | for script in (self.public_make_defaults, self.private_make_defaults): |
| 314 | value = sh_getvar(script, var) |
| 315 | if value: |
| 316 | for v in value.split(): |
| 317 | getattr(self, attr).add(v) |
| 318 | |
| 319 | if 'whiskers' in self.ec_firmware_extras: |
| 320 | self.ec_firmware_extras.remove('whiskers') |
| 321 | self.detachable_base_build_target = 'whiskers' |
| 322 | else: |
| 323 | self.detachable_base_build_target = None |
| 324 | |
| 325 | self.ec_build_target = ' '.join(self.ec_firmwares) or None |
| 326 | self.ec_extras_build_target = sorted(list(self.ec_firmware_extras |
| 327 | | self.pd_firmwares)) or None |
| 328 | |
Jack Rosenthal | be2f127 | 2020-06-10 09:31:09 -0600 | [diff] [blame] | 329 | def write_file(self, overlay_flags, path, file_contents, |
| 330 | make_ebuild_symlink=False): |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 331 | dirs = [] |
| 332 | if overlay_flags & M_PUBLIC: |
| 333 | dirs += [self.public_overlay] |
| 334 | if overlay_flags & M_PRIVATE: |
| 335 | dirs += [self.private_overlay] |
| 336 | for d in dirs: |
Jack Rosenthal | be2f127 | 2020-06-10 09:31:09 -0600 | [diff] [blame] | 337 | write_file(d / path, file_contents, |
| 338 | make_ebuild_symlink=make_ebuild_symlink) |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 339 | |
| 340 | |
| 341 | class Dut: |
| 342 | def __init__(self, hostname, checkout, port=22): |
| 343 | self.ssh_hostname = hostname |
| 344 | |
| 345 | id_source = checkout / 'chromite' / 'ssh_keys' / 'testing_rsa' |
| 346 | with open(id_source, 'rb') as f: |
| 347 | id_contents = f.read() |
| 348 | |
| 349 | with tempfile.NamedTemporaryFile(mode='wb', delete=False) as tmpfile: |
| 350 | tmpfile.write(id_contents) |
| 351 | self.ssh_identity = tmpfile.name |
| 352 | |
| 353 | with tempfile.NamedTemporaryFile(delete=False) as tmpfile: |
| 354 | self.ssh_known_hosts_file = tmpfile.name |
| 355 | |
| 356 | self.ssh_port = port |
| 357 | |
| 358 | # Check connectivity. |
| 359 | log('Checking SSH connectivity to DUT...') |
| 360 | self.run_command(['/bin/true']) |
| 361 | |
| 362 | # Linter is unaware that we set check=True in kwargs. |
| 363 | # pylint: disable=subprocess-run-check |
| 364 | def run_command(self, argv, *args, **kwargs): |
| 365 | kwargs.setdefault('check', True) |
| 366 | kwargs.setdefault('stdout', subprocess.PIPE) |
| 367 | kwargs.setdefault('encoding', 'utf-8') |
| 368 | quoted_argv = [shlex.quote(arg) for arg in argv] |
| 369 | return subprocess.run(['ssh', |
| 370 | '-p', '{}'.format(self.ssh_port), |
| 371 | '-i', self.ssh_identity, |
| 372 | '-o', 'UserKnownHostsFile={}'.format( |
| 373 | self.ssh_known_hosts_file), |
| 374 | '-o', 'StrictHostKeyChecking=no', |
| 375 | '-o', 'CheckHostIP=no', |
| 376 | '-o', 'ConnectTimeout=10', |
| 377 | 'root@{}'.format(self.ssh_hostname)] + quoted_argv, |
| 378 | *args, **kwargs) |
| 379 | # pylint: enable=subprocess-run-check |
| 380 | |
| 381 | |
| 382 | class DeviceConfig: |
| 383 | ATTRS = { |
| 384 | 'brand_code': ['mosys', 'platform', 'brand'], |
| 385 | 'model': ['mosys', 'platform', 'model'], |
| 386 | 'lsb_release': ['cat', '/etc/lsb-release'], |
| 387 | 'smbios_name': ['cat', '/sys/class/dmi/id/product_name'], |
| 388 | 'fdt_compatible_raw': ['cat', '/proc/device-tree/compatible'], |
| 389 | 'arc_build_props': ['cat', '/usr/share/arc/properties/build.prop'], |
| 390 | 'mosys_psu_type': ['mosys', 'psu', 'type'], |
| 391 | 'whitelabel_tag': ['vpd_get_value', 'whitelabel_tag'], |
| 392 | 'customization_id': ['vpd_get_value', 'customization_id'], |
| 393 | 'cras_config_dir': ['sh', '/etc/cras/get_device_config_dir'], |
| 394 | 'internal_ucm_suffix': ['sh', '/etc/cras/get_internal_ucm_suffix'], |
| 395 | # disgusting, but whatever... |
| 396 | 'powerd_raw': |
| 397 | ['python3', '-c', |
| 398 | 'import os;' |
| 399 | 'import json;' |
| 400 | 'print(json.dumps(' |
| 401 | '{f.replace("_", "-"): open("/usr/share/power_manager/board_specific/"+f).read().rstrip()' |
| 402 | ' for f in os.listdir("/usr/share/power_manager/board_specific")}))'], |
| 403 | } |
| 404 | |
| 405 | @classmethod |
| 406 | def from_dut(cls, dut): |
| 407 | slf = cls() |
| 408 | for attr, cmd in cls.ATTRS.items(): |
| 409 | try: |
| 410 | log('Running {!r} on DUT...'.format(cmd)) |
| 411 | res = dut.run_command(cmd) |
| 412 | except subprocess.CalledProcessError: |
| 413 | setattr(slf, attr, None) |
| 414 | else: |
| 415 | setattr(slf, attr, res.stdout.strip()) |
| 416 | return slf |
| 417 | |
| 418 | def __str__(self): |
| 419 | return 'DeviceConfig({})'.format( |
| 420 | ', '.join('{}={!r}'.format(attr, getattr(self, attr)) |
| 421 | for attr in self.ATTRS)) |
| 422 | |
| 423 | def lsb_val(self, name, default=None): |
| 424 | for item in self.lsb_release.splitlines(): |
| 425 | k, _, v = item.partition('=') |
| 426 | if k == name: |
| 427 | return v |
| 428 | return default |
| 429 | |
| 430 | def arc_build_prop(self, name, default=None): |
| 431 | for line in self.arc_build_props.splitlines(): |
| 432 | if '#' in line: |
| 433 | line, _, _ = line.partition('#') |
| 434 | line = line.strip() |
| 435 | if line.startswith('{}='.format(name)): |
| 436 | _, _, val = line.partition('=') |
| 437 | return val |
| 438 | return default |
| 439 | |
| 440 | |
| 441 | def genconf_first_api_level(_, overlay): |
| 442 | if overlay.board_name in ('atlas', 'nocturne'): |
| 443 | return '28' |
| 444 | return '25' |
| 445 | |
| 446 | |
| 447 | def genconf_dt_compatible_match(device, overlay): |
| 448 | if not device.fdt_compatible_raw: |
| 449 | return None |
| 450 | compatible_strings = device.fdt_compatible_raw.strip('\x00').split('\x00') |
| 451 | compatible_strings.sort(key=lambda s: (s.startswith('google'), |
| 452 | 'rev' not in s, |
| 453 | 'sku' not in s, |
| 454 | overlay.board_name in s, |
| 455 | -len(s))) |
| 456 | return compatible_strings[-1] |
| 457 | |
| 458 | |
| 459 | def genconf_psu_type(device, _): |
| 460 | if device.mosys_psu_type: |
| 461 | return device.mosys_psu_type |
| 462 | devicetype = device.lsb_val('DEVICETYPE') |
| 463 | if devicetype == 'CHROMEBOOK': |
| 464 | return 'battery' |
| 465 | if devicetype in ('CHROMEBIT', 'CHROMEBASE', 'CHROMEBOX'): |
| 466 | return 'AC_only' |
| 467 | return None |
| 468 | |
| 469 | |
Nikolai Artemiev | adc67a9 | 2021-05-04 16:43:15 +1000 | [diff] [blame] | 470 | def genconf_form_factor(device, _): |
| 471 | devicetype = device.lsb_val('DEVICETYPE') |
| 472 | if devicetype in ('REFERENCE', 'CHROMEBOOK'): |
| 473 | return 'CHROMEBOOK' |
| 474 | if devicetype in ('CHROMEBIT', 'CHROMEBASE', 'CHROMEBOX'): |
| 475 | return devicetype |
| 476 | return None |
| 477 | |
| 478 | |
Jack Rosenthal | 2ed088c | 2020-08-11 12:41:17 -0600 | [diff] [blame] | 479 | def genconf_has_backlight(device, _): |
| 480 | devicetype = device.lsb_val('DEVICETYPE') |
| 481 | return devicetype not in ('CHROMEBIT', 'CHROMEBOX') |
| 482 | |
| 483 | |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 484 | def genconf_fp_board(_, overlay): |
| 485 | if overlay.fpmcu_firmware: |
| 486 | return ' '.join(overlay.fpmcu_firmware) |
| 487 | return None |
| 488 | |
| 489 | |
| 490 | def genconf_fp_type(_, overlay): |
| 491 | if 'fp_on_power_button' in overlay.use_flags: |
| 492 | return 'on-power-button' |
| 493 | if overlay.fpmcu_firmware: |
| 494 | return 'stand-alone' |
| 495 | return None |
| 496 | |
| 497 | |
| 498 | def genconf_fp_location(_, overlay): |
| 499 | if overlay.board_name == 'nocturne': |
| 500 | return 'power-button-top-left' |
| 501 | return None |
| 502 | |
| 503 | |
| 504 | def genconf_signature_id(device, _): |
| 505 | if device.whitelabel_tag: |
| 506 | return device.whitelabel_tag.upper() |
| 507 | if device.customization_id: |
| 508 | return device.customization_id.upper().partition('-')[0] |
| 509 | return device.model |
| 510 | |
| 511 | |
| 512 | def genconf_cras_config_dir(device, _): |
| 513 | prefix = '/etc/cras/' |
| 514 | if device.cras_config_dir and device.cras_config_dir.startswith(prefix): |
| 515 | return device.cras_config_dir[len(prefix):] |
| 516 | if device.cras_config_dir: |
| 517 | return '../../{}'.format(device.cras_config_dir) |
| 518 | return None |
| 519 | |
| 520 | |
| 521 | def genconf_powerd_settings(device, overlay): |
| 522 | if not device.powerd_raw: |
| 523 | d = {} |
| 524 | else: |
| 525 | d = json.loads(device.powerd_raw) |
Jack Rosenthal | 263ba0e | 2020-07-06 10:49:15 -0600 | [diff] [blame] | 526 | |
| 527 | # 2-tuples of (use_flag, powerd_option) |
| 528 | # Source of truth is power_manager ebuild. |
| 529 | use_flag_settings = [ |
| 530 | ('als', 'has-ambient-light-sensor'), |
| 531 | ('cras', 'use-cras'), |
| 532 | ('has_keyboard_backlight', 'has-keyboard-backlight'), |
| 533 | ('legacy_power_button', 'legacy-power-button'), |
| 534 | ('mosys_eventlog', 'mosys-eventlog'), |
| 535 | ] |
| 536 | |
| 537 | for flag, powerd_setting in use_flag_settings: |
| 538 | if flag in overlay.use_flags: |
| 539 | d[powerd_setting] = '1' |
| 540 | |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 541 | return d |
| 542 | |
| 543 | |
Jack Rosenthal | 36a4c24 | 2020-06-10 20:55:06 -0600 | [diff] [blame] | 544 | def genconf_whitelabel_tag(device, _): |
| 545 | # Devices with a Customization ID are not compatible with whitelabel |
| 546 | # tags. |
| 547 | if device.customization_id: |
| 548 | return None |
| 549 | return device.whitelabel_tag or None |
| 550 | |
| 551 | |
Jack Rosenthal | 19b40eb | 2020-06-24 15:38:45 -0600 | [diff] [blame] | 552 | def genconf_wallpaper_id(device, overlay): |
| 553 | wallpapers_dir = (overlay.checkout / 'src' / 'platform' / 'chromeos-assets' |
| 554 | / 'wallpaper' / 'large') |
| 555 | assert wallpapers_dir.is_dir() |
| 556 | for wallpaper_id in (overlay.board_name, device.model): |
| 557 | if (wallpapers_dir / f'{wallpaper_id}.jpg').is_file(): |
| 558 | return wallpaper_id |
| 559 | return None |
| 560 | |
| 561 | |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 562 | M_PUBLIC = (1 << 0) |
| 563 | M_PRIVATE = (1 << 1) |
| 564 | |
| 565 | |
| 566 | genconf_schema = { |
| 567 | 'name': (M_PUBLIC | M_PRIVATE, lambda d, _: d.model), |
| 568 | 'brand-code': (M_PUBLIC, lambda d, _: d.brand_code), |
| 569 | 'arc': { |
| 570 | 'build-properties': { |
| 571 | 'device': (M_PRIVATE, lambda d, _: |
| 572 | d.arc_build_prop('ro.product.device')), |
| 573 | 'marketing-name': (M_PRIVATE, lambda d, _: |
| 574 | d.arc_build_prop('ro.product.model')), |
| 575 | 'oem': (M_PRIVATE, |
| 576 | lambda d, _: d.arc_build_prop('ro.product.brand')), |
| 577 | 'first-api-level': (M_PRIVATE, genconf_first_api_level), |
| 578 | 'metrics-tag': (M_PRIVATE, |
| 579 | lambda d, _: d.arc_build_prop('ro.product.board')), |
| 580 | 'product': (M_PRIVATE, lambda d, _: |
| 581 | d.arc_build_prop('ro.product.name')), |
| 582 | }, |
| 583 | }, |
| 584 | 'audio': { |
| 585 | 'main': { |
| 586 | 'cras-config-dir': (M_PUBLIC, genconf_cras_config_dir), |
| 587 | 'ucm-suffix': (M_PUBLIC, lambda d, _: d.internal_ucm_suffix), |
| 588 | }, |
| 589 | }, |
| 590 | 'fingerprint': { |
| 591 | 'board': (M_PUBLIC, genconf_fp_board), |
| 592 | 'fingerprint-sensor-type': (M_PUBLIC, genconf_fp_type), |
| 593 | 'sensor-location': (M_PUBLIC, genconf_fp_location), |
| 594 | }, |
| 595 | 'firmware': { |
| 596 | 'image-name': (M_PUBLIC, lambda d, _: d.model), |
| 597 | 'name': (M_PRIVATE, lambda d, _: d.model), |
| 598 | 'bcs-overlay': (M_PRIVATE, lambda _, b: |
| 599 | f'overlay-{b.board_name}-private'), |
| 600 | 'ec-ro-image': (M_PRIVATE, lambda _, b: b.bcs_ec), |
| 601 | 'pd-ro-image': (M_PRIVATE, lambda _, b: b.bcs_pd), |
| 602 | 'main-ro-image': (M_PRIVATE, lambda _, b: b.bcs_main_ro), |
| 603 | 'main-rw-image': (M_PRIVATE, lambda _, b: b.bcs_main_rw), |
| 604 | 'build-targets': { |
| 605 | 'base': (M_PUBLIC, lambda _, b: b.detachable_base_build_target), |
| 606 | 'coreboot': (M_PUBLIC, lambda _, b: b.board_name), |
| 607 | 'depthcharge': (M_PUBLIC, lambda _, b: b.board_name), |
| 608 | 'ec': (M_PUBLIC, lambda _, b: b.ec_build_target), |
| 609 | 'ec_extras': (M_PUBLIC, lambda _, b: b.ec_extras_build_target), |
| 610 | }, |
| 611 | }, |
| 612 | 'firmware-signing': { |
| 613 | 'key-id': (M_PRIVATE, lambda d, _: d.model.upper()), |
| 614 | 'signature-id': (M_PRIVATE, genconf_signature_id), |
| 615 | }, |
| 616 | 'hardware-properties': { |
Nikolai Artemiev | adc67a9 | 2021-05-04 16:43:15 +1000 | [diff] [blame] | 617 | 'form-factor': (M_PUBLIC, genconf_form_factor), |
Jack Rosenthal | 25e72da | 2020-08-20 20:52:04 -0600 | [diff] [blame] | 618 | 'has-backlight': (M_PUBLIC, genconf_has_backlight), |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 619 | 'psu-type': (M_PUBLIC, genconf_psu_type), |
| 620 | }, |
| 621 | 'identity': { |
| 622 | 'platform-name': (M_PUBLIC, lambda _, b: b.mosys_platform), |
Jack Rosenthal | 9ec0ecd | 2020-06-17 18:13:56 -0600 | [diff] [blame] | 623 | 'smbios-name-match': (M_PUBLIC, lambda d, _: d.smbios_name), |
| 624 | 'device-tree-compatible-match': (M_PUBLIC, genconf_dt_compatible_match), |
| 625 | 'customization-id': (M_PUBLIC, lambda d, _: d.customization_id or None), |
| 626 | 'whitelabel-tag': (M_PUBLIC, genconf_whitelabel_tag), |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 627 | }, |
| 628 | 'power': (M_PUBLIC, genconf_powerd_settings), |
Jack Rosenthal | 19b40eb | 2020-06-24 15:38:45 -0600 | [diff] [blame] | 629 | 'wallpaper': (M_PRIVATE, genconf_wallpaper_id), |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 630 | } |
| 631 | |
| 632 | |
| 633 | def genconf(schema, device_conf, overlay_conf): |
| 634 | |
| 635 | def qualifies_as_value(v): |
| 636 | return v is not None and v != {} |
| 637 | |
| 638 | if isinstance(schema, dict): |
| 639 | pub, priv = {}, {} |
| 640 | for k, v in schema.items(): |
| 641 | pub_r, priv_r = genconf(v, device_conf, overlay_conf) |
| 642 | if qualifies_as_value(pub_r): |
| 643 | pub[k] = pub_r |
| 644 | if qualifies_as_value(priv_r): |
| 645 | priv[k] = priv_r |
| 646 | return pub, priv |
| 647 | |
| 648 | if isinstance(schema, tuple): |
| 649 | pub, priv = None, None |
| 650 | flags, func = schema |
| 651 | value = func(device_conf, overlay_conf) |
| 652 | if flags & M_PUBLIC: |
| 653 | pub = value |
| 654 | if flags & M_PRIVATE: |
| 655 | priv = value |
| 656 | return pub, priv |
| 657 | |
| 658 | |
| 659 | def validate_gs_uri(uri): |
| 660 | log('Validating {}...'.format(uri)) |
| 661 | subprocess.run(['gsutil', 'stat', uri], check=True, stdout=subprocess.DEVNULL) |
| 662 | |
| 663 | |
| 664 | def parse_opts(argv): |
| 665 | parser = argparse.ArgumentParser() |
| 666 | parser.add_argument('--cros-checkout', |
| 667 | type=pathlib.Path, |
| 668 | default=pathlib.Path(os.getenv('HOME')) / 'trunk', |
| 669 | help='Location of the ChromeOS checkout') |
| 670 | parser.add_argument('--dut', '-d', |
| 671 | type=str, |
| 672 | required=True, |
| 673 | help='Hostname of DUT to use for querying and testing.') |
| 674 | parser.add_argument('--dut-ssh-port', type=int, default=22, |
| 675 | help='SSH port to use on the dut.') |
| 676 | parser.add_argument('--board', '-b', |
| 677 | type=str, |
| 678 | required=True, |
| 679 | help='Board name to convert.') |
| 680 | parser.add_argument('--mosys-platform', type=str, required=True) |
| 681 | parser.add_argument('--dry-run', |
| 682 | action='store_true', |
| 683 | default=False, |
| 684 | help='Dry run') |
| 685 | return parser.parse_args(argv) |
| 686 | |
| 687 | |
| 688 | def main(argv): |
| 689 | opts = parse_opts(argv) |
| 690 | |
| 691 | overlays = BoardOverlays(opts.board, opts.cros_checkout, opts.mosys_platform) |
| 692 | dut = Dut(opts.dut, opts.cros_checkout, port=opts.dut_ssh_port) |
| 693 | |
| 694 | log('Loading configuration from DUT...') |
| 695 | dut_config = DeviceConfig.from_dut(dut) |
| 696 | log('Got configuration: {}'.format(dut_config)) |
| 697 | |
| 698 | assert dut_config.lsb_val('CHROMEOS_RELEASE_BOARD') == opts.board |
| 699 | assert dut_config.lsb_val('CHROMEOS_RELEASE_UNIBUILD', '0') != '1' |
| 700 | |
| 701 | log('Generating chromeos-config values...') |
| 702 | public_config, private_config = genconf(genconf_schema, dut_config, overlays) |
| 703 | |
| 704 | public_config_yaml = format_yaml(public_config) |
| 705 | private_config_yaml = format_yaml(private_config) |
| 706 | log('Got public config: \n{}'.format(public_config_yaml)) |
| 707 | log('Got private config: \n{}'.format(private_config_yaml)) |
| 708 | |
| 709 | log('Generating ebuilds...') |
| 710 | |
| 711 | public_vpackage = generate_vpackage(('chromeos-base/chromeos-config-bsp', )) |
| 712 | private_vpackage = generate_vpackage( |
| 713 | ('chromeos-base/chromeos-config-bsp', |
| 714 | 'chromeos-base/chromeos-config-bsp-private')) |
| 715 | log('Got public vpackage: \n{}'.format(public_vpackage)) |
| 716 | log('Got private vpackage: \n{}'.format(private_vpackage)) |
| 717 | |
| 718 | public_bsp_ebuild = generate_bsp_ebuild() |
| 719 | private_bsp_ebuild = generate_bsp_ebuild(private=True) |
| 720 | log('Got public bsp_ebuild: \n{}'.format(public_bsp_ebuild)) |
| 721 | log('Got private bsp_ebuild: \n{}'.format(private_bsp_ebuild)) |
| 722 | |
| 723 | firmware_ebuild = generate_firmware_ebuild(opts.board) |
| 724 | log('Got firmware ebuild: \n{}'.format(firmware_ebuild)) |
| 725 | |
| 726 | public_make_defaults = generate_make_defaults(overlays.public_make_defaults) |
| 727 | log('Got public make defaults: \n{}'.format(public_make_defaults)) |
| 728 | private_make_defaults = generate_make_defaults(overlays.private_make_defaults) |
| 729 | log('Got private make defaults: \n{}'.format(private_make_defaults)) |
| 730 | |
| 731 | cros_config = CrosConfig(public_config_yaml, private_config_yaml) |
| 732 | firmware_srcuris = cros_config.run_host_command('get-firmware-uris') |
| 733 | log('Got firmware URIs: {}'.format(firmware_srcuris)) |
| 734 | |
| 735 | log('Validating firmware srcuris...') |
| 736 | for uri in firmware_srcuris.split(): |
| 737 | validate_gs_uri(uri) |
| 738 | |
| 739 | firmware_srcuris_path = (overlays.firmware_ebuild_path.parent |
| 740 | / 'files' / 'srcuris') |
| 741 | |
| 742 | if opts.dry_run: |
| 743 | return |
| 744 | |
| 745 | overlays.write_file( |
| 746 | M_PUBLIC, 'chromeos-base/chromeos-config-bsp/files/model.yaml', |
| 747 | public_config_yaml) |
| 748 | overlays.write_file( |
| 749 | M_PRIVATE, 'chromeos-base/chromeos-config-bsp-private/files/model.yaml', |
| 750 | private_config_yaml) |
| 751 | overlays.write_file( |
Jack Rosenthal | be2f127 | 2020-06-10 09:31:09 -0600 | [diff] [blame] | 752 | M_PUBLIC, 'virtual/chromeos-config-bsp/chromeos-config-bsp-2.ebuild', |
| 753 | public_vpackage, make_ebuild_symlink=True) |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 754 | overlays.write_file( |
Jack Rosenthal | be2f127 | 2020-06-10 09:31:09 -0600 | [diff] [blame] | 755 | M_PRIVATE, 'virtual/chromeos-config-bsp/chromeos-config-bsp-3.ebuild', |
| 756 | private_vpackage, make_ebuild_symlink=True) |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 757 | overlays.write_file( |
| 758 | M_PUBLIC, |
| 759 | 'chromeos-base/chromeos-config-bsp/chromeos-config-bsp-9999.ebuild', |
| 760 | public_bsp_ebuild) |
| 761 | overlays.write_file( |
| 762 | M_PRIVATE, |
| 763 | 'chromeos-base/chromeos-config-bsp-private/chromeos-config-bsp-private-9999.ebuild', |
| 764 | private_bsp_ebuild) |
| 765 | write_file(overlays.firmware_ebuild_path, firmware_ebuild) |
Andrew Lamb | bc1c80a | 2020-10-23 12:21:49 -0600 | [diff] [blame] | 766 | write_file(firmware_srcuris_path, ''.join('{}\n'.format(uri) for uri in firmware_srcuris.split())) |
Jack Rosenthal | 543bb06 | 2020-06-05 15:30:09 -0600 | [diff] [blame] | 767 | write_file(overlays.public_make_defaults_file, public_make_defaults) |
| 768 | write_file(overlays.private_make_defaults_file, private_make_defaults) |
| 769 | |
| 770 | |
| 771 | if __name__ == '__main__': |
| 772 | main(sys.argv[1:]) |