blob: fd0480e0265e9a0f30a5070b83b15e030f698c5c [file] [log] [blame]
#!/usr/bin/env python2
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Converts information in firmware ebuilds to a master configuration.
This tool converts information in a firmware ebuild, such as:
CROS_FIRMWARE_BCS_OVERLAY="overlay-reef-private"
CROS_FIRMWARE_MAIN_IMAGE="bcs://Reef.9042.72.0.tbz2"
CROS_FIRMWARE_EC_IMAGE="bcs://Reef_EC.9042.72.0.tbz2"
CROS_FIRMWARE_STABLE_MAIN_VERSION="Google_Reef.9042.72.0"
CROS_FIRMWARE_STABLE_EC_VERSION="reef_v1.1.5891-37adb2a"
CROS_FIRMWARE_SCRIPT="updater4.sh"
and converts it to a firmware node in the master configuration:
firmware {
bcs-overlay = "overlay-reef-private";
main-image = "bcs://Reef.9042.72.0.tbz2";
ec-image = "bcs://Reef_EC.9042.72.0.tbz2";
stable-main-version = "Google_Reef.9042.72.0";
stable-ec-version = "reef_v1.1.5891-37adb2a";
script = "updater4.sh";
};
It operates on one or more models.
"""
from __future__ import print_function
import glob
import os
import re
import sys
from chromite.lib import commandline
from chromite.lib import constants
from chromite.lib import cros_logging as logging
from chromite.lib import osutils
CROS_FIRMWARE = 'CROS_FIRMWARE_'
BCS_PREFIX = 'bcs://'
RE_VARIANT_NAME = re.compile('overlay-([a-z]+)-private')
# Translates from shell variable to property. See binding document at
# src/platform2/chromeos-config/README.md
TRANSLATE_TO_PROPERTY = {
'BCS_OVERLAY': 'bcs-overlay',
'MAIN_IMAGE': 'main-image',
'MAIN_RW_IMAGE': 'main-rw-image',
'EC_IMAGE': 'ec-image',
'PD_IMAGE': 'pd-image',
'SCRIPT': 'script',
'PLATFORM': '',
'STABLE_MAIN_VERSION': 'stable-main-version',
'STABLE_EC_VERSION': 'stable-ec-version',
'STABLE_PD_VERSION': 'stable-pd-version',
'BUILD_MAIN_RW_IMAGE': 'build-main-rw-image',
'EXTRA_LIST': 'extras',
}
def _ParseArgs(argv):
"""Parse the available arguments.
Invalid arguments or -h cause this function to print a message and exit.
Args:
argv: List of string arguments (excluding program name / argv[0]).
Returns:
argparse.Namespace object containing the attributes.
"""
parser = commandline.ArgumentParser(description=__doc__)
parser.add_argument('-a', '--all', action='store_true',
help='Convert all models that can be found')
parser.add_argument('-m', '--model', type=str, dest='models',
action='append', help='Model name to convert')
opts = parser.parse_args(argv)
if not (opts.models or opts.all):
raise parser.error('Please use -m to specify model(s) or -a for all')
opts.Freeze()
return opts
class ModelConverterError(Exception):
"""Exception returned by FirmwarePacker when something goes wrong"""
class ModelConverter(object):
"""Handles conversion from an ebuild to a master configuration node.
Private members:
_args: argparse.Namespace object containing parsed arguments.
_indent: Current indent level for output, in terms of number of tabs.
_models: List of models to generate output for, or None to do all.
_path_format: Format string to use to find the path to an ebuild, given its
model name.
_srcpath: Path source Chrome OS source code ('src' directory).
_urls: List of URLs to fetch from GCS.
"""
def __init__(self, models):
self._models = models
self._indent = 0
self._srcpath = os.path.realpath(os.path.join(constants.SOURCE_ROOT, 'src'))
self._urls = []
self._path_format = ('private-overlays/overlay-%(model)s-private/'
'chromeos-base/chromeos-firmware-%(model)s/'
'chromeos-firmware-%(model)s*.ebuild')
def _AddLine(self, line):
"""Add a line to the output.
This keeps track of indenting automatically. Indentation increases for
lines ending in '{' and descreases for lines ending with '};'
Args:
line: Line of text to add.
"""
if line == '};':
self._indent -= 1
indent_tabs = '\t' * self._indent if line else ''
print('%s%s' % (indent_tabs, line))
if line.endswith('{'):
self._indent += 1
@staticmethod
def _RaiseModelError(model, msg):
"""Helper function to raise a ModelConverterError.
Args:
model: Model name the error relates to.
msg: Message string to include in the exception.
"""
raise ModelConverterError('Model: %s: %s' % (model, msg))
def _AddFile(self, model, fname):
"""Add a new URL to our list of files to download.
This adds the full URL to the given file, based on known locations in GCS.
Args:
model: Name of model (e.g. "reef").
fname: Filename of file to download
"""
url = ('gs://chromeos-binaries/HOME/bcs-%s-private/overlay-%s-private/'
'chromeos-base/chromeos-firmware-%s/%s' %
(model, model, model, fname))
self._urls.append(url)
def _OutputNode(self, model, shell_vars):
"""Output a firmware node for a model.
This outputs each property on its own line.
Args:
model: Name of mode (and therefore node).
shell_vars: Shell varaible dict:
key: Shell variable name (e.g. CROS_FIRMWARE_MAIN_IMAGE)
value: Value of variable (e.g. 'bcs://Reef.9042.87.0.tbz2')
"""
for var, value in sorted(shell_vars.iteritems()):
if not var.startswith(CROS_FIRMWARE):
continue
var = var[len(CROS_FIRMWARE):]
prop = TRANSLATE_TO_PROPERTY.get(var)
if prop is None:
self._RaiseModelError(model, 'Shell variable %s is unknown' % var)
if prop and value:
# This boolean property has no value.
if var == 'BUILD_MAIN_RW_IMAGE':
self._AddLine('%s;' % prop)
# This property needs processing to turn it into a proper string list.
elif var == 'EXTRA_LIST':
self._AddLine('%s = "%s";' % (prop, '", "'.join(value.split(';'))))
# Normal properties just have a property name and string value.
else:
self._AddLine('%s = "%s";' % (prop, value))
if value.startswith(BCS_PREFIX):
self._AddFile(model, value[len(BCS_PREFIX):])
def _ProcessModel(self, model):
"""Process an ebuild for a model.
This finds the ebuild for the given model and outputs a configuration node
for it.
Args:
model: Model to process.
Returns:
Partial pathname of the ebuild that was processed (the part after
self._src).
"""
# Deal with something like reef-uni which does not have its own
# chromeos-firmware-reef-uni ebuild but uses chromeos-firmware-reef.
ebuild_model = re.sub('-uni$', '', model)
pathname = os.path.join(
self._srcpath, self._path_format % {'model': ebuild_model})
ebuild = [fname for fname in glob.glob(pathname) if '9999' not in fname]
if not ebuild:
self._RaiseModelError(model, 'Could not find ebuild at "%s"' % pathname)
if len(ebuild) != 1:
self._RaiseModelError(model, 'Expected a single ebuild, found: %s' %
'\n'.join(ebuild))
fname = ebuild[0]
env = {}
for var in ['FILESDIR', 'ROOT', 'SYSROOT']:
env[var] = '${%s}' % var
shell_vars = osutils.SourceEnvironment(
fname,
[CROS_FIRMWARE + s for s in TRANSLATE_TO_PROPERTY.keys()],
env=env)
self._AddLine('%s {' % ebuild_model)
self._AddLine('firmware {')
# This is only used in zgb and is deprecated, so skip it.
if '%sEC_VERSION' % CROS_FIRMWARE in shell_vars:
self._RaiseModelError(model, 'Manual %sEC_VERSION is not supported' %
CROS_FIRMWARE)
self._OutputNode(model, shell_vars)
self._AddLine('};')
self._AddLine('};')
self._AddLine('')
return fname[len(self._srcpath) + 1:]
def Start(self):
"""Start processing ebuilds.
Args:
argv: Program arguments (without program name).
"""
if self._models:
self._AddLine('&models {')
# Process all models, saving the first one's ebuild path. The first one
# should be the reference board.
ebuild_pathname = None
for model in self._models:
fname = self._ProcessModel(model)
if not ebuild_pathname:
ebuild_pathname = fname
self._AddLine('};')
print()
print('Run this command in chroot to regenerate the manifest:')
print('FIRMWARE_UNIBUILD="%s" ebuild %s manifest' %
(' '.join(self._models),
os.path.join(constants.CHROOT_SOURCE_ROOT, 'src',
ebuild_pathname)))
else:
pathname = os.path.join(self._srcpath,
'private-overlays/overlay-*-private')
for dirname in sorted(glob.glob(pathname)):
m = RE_VARIANT_NAME.search(dirname)
if m:
model = m.group(1)
try:
self._ProcessModel(model)
except ModelConverterError as e:
logging.error(str(e))
return 0
def main(argv):
args = _ParseArgs(argv)
conv = ModelConverter(args.models)
conv.Start()
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))