blob: 841aa5f0789908e67fa78c06aa04d021073131e7 [file] [log] [blame]
#!/usr/bin/python
#
# Copyright 2015 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.
import argparse
from collections import namedtuple
import logging
import os
import re
import subprocess
import sys
import yaml
import factory_common # pylint: disable=W0611
from cros.factory.tools.build_board import BuildBoard
from cros.factory.utils import file_utils
from cros.factory.utils import process_utils
BundleFields = ['board', 'base_board', 'created_date', 'factory_toolkit',
'test_image', 'release_image', 'EC', 'BIOS', 'PD']
BundleInfo = namedtuple('BundleInfo', BundleFields)
DESCRIPTION = """Lists factory bundle information for a board, or
boards with the same base board, or all boards.
Public base board information:
http://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices
"""
EXAMPLES = r"""Examples:
1. Lists factory bundle info for all boards:
py/tools/factory_bundle_info.py
2. Lists factory bundle info for all boards based on rambi:
py/tools/factory_bundle_info.py --base_board rambi
3. Lists factory bundle info for squawks:
py/tools/factory_bundle_info.py --board squawks
4. Lists factory bundle info for all boards to a HTML file:
py/tools/factory_bundle_info.py --html_file=bundle.html
5. Lists factory bundle info using a local boards.yaml file:
py/tools/factory_bundle_info.py --boards_yaml=boards.yaml
"""
def _GetFactoryBundleInfo(board, factory_branch, repo_sync):
"""Gets factory bundle info from the bundle README file.
A typical README file has the following format. This function extracts
Board, Bundle, Factory toolkit, Test image, BIOS and EC info, then returns
a BundleInfo namedtuple. Some board might have PD firmware.
***
*
* VITAL INFORMATION
*
***
Board: squawks
Bundle: 20141229_pvt (created by xxxx, ...)
Factory toolkit: 5517.274.0
Test image: 5978.115.0
Factory updater MD5SUM: 950b569641e1e4c944c73b3ea39cfe50
Stateful partition size: 975 MiB (458 MiB free = 46% free)
Stateful partition inodes: 65536 nodes (46832 free)
Factory install shim: 5517.89.0 (mp)
Release (FSI): 6310.68.0
Release (FSI) BIOS: Google_Squawks.5216.152.22
Release (FSI) EC: squawks_v1.6.150-0d8dbf2
firmware/chromeos-firmwareupdate BIOS: Google_Squawks.5216.152.22
firmware/chromeos-firmwareupdate EC: squawks_v1.6.150-0d8dbf2
firmware/chromeos-firmwareupdate PD: squawks_v1.6.150-0d8dbf2
***
*
* NOTE
*
***
This bundle should put image.net.bin into factory image.
Please use the following commands after you change server ip for
netboot image.
...
Args:
board: the board name to get factory bundle info.
factory_branch: the factory branch of the board.
repo_sync: whether to 'repo sync' under board private overlays.
Returns: A BundleInfo tuple.
"""
overlay_relpath = os.path.join(
os.environ['CROS_WORKON_SRCROOT'], 'src',
BuildBoard(board).overlay_relpath)
bundle_readme_relpath = os.path.join(
'chromeos-base', 'chromeos-factory-board', 'files', 'bundle', 'README')
# Do repo sync -n (fetch only, don't update working tree).
if repo_sync:
process_utils.Spawn(
'repo sync -n .',
log=True, cwd=overlay_relpath, shell=True, check_call=True)
try:
bundle_readme = process_utils.CheckOutput(
['git', 'show', 'remotes/cros-internal/%s:%s' % (
factory_branch, bundle_readme_relpath)],
cwd=overlay_relpath, ignore_stderr=True)
except subprocess.CalledProcessError:
logging.warn(
'Bundle README not found for %s on branch %s', board, factory_branch)
return None
# Gets vital info contents.
vital_info = re.match(
# Vital info section header
r'(?:\*\*\*\n\*\n\* VITAL INFORMATION\n\*\n\*\*\*\n)'
# Anything up to (but not including) the next section header
r'((?:(?!\*\*\*).)+)', bundle_readme, re.DOTALL)
# Group(1) contains the 'KEY: VALUE (COMMENT)' pairs in vital contents.
RE_KEY = r'([\w \(\)\./_-]+)'
RE_VALUE = r'([\w \._-]+)'
RE_IGNORE_COMMENT = r'(?: \(.*?\))?'
RE_VITAL_CONTENTS = RE_KEY + r':\s+' + RE_VALUE + RE_IGNORE_COMMENT + r'\n'
vital_key_value = re.findall(RE_VITAL_CONTENTS, vital_info.group(1))
vital_dict = dict((k, v) for k, v in vital_key_value)
fsi_prefix = 'Release (FSI)'
fw_updater_prefix = 'firmware/chromeos-firmwareupdate'
fw_labels = ['BIOS', 'EC', 'PD']
fw_versions = dict.fromkeys(fw_labels, 'NA')
for label in fw_labels:
# The firmware/chromeos-firmwareupdate will be used if it exists.
# Otherwise, firmware updater extracted from release image will be used.
for prefix in [fw_updater_prefix, fsi_prefix]:
if prefix + ' ' + label in vital_dict:
fw_versions[label] = vital_dict[prefix + ' ' + label]
break
# Before toolkit was invented, there is only
# factory image = toolkit + test image.
if 'Factory image base' in vital_dict:
toolkit = test_image = vital_dict['Factory image base']
else:
toolkit = vital_dict['Factory toolkit']
test_image = vital_dict['Test image']
base_board, _ = _ExtractBaseboardAndVersion(factory_branch)
return BundleInfo(vital_dict['Board'], base_board,
vital_dict['Bundle'], toolkit, test_image,
vital_dict[fsi_prefix], fw_versions['EC'],
fw_versions['BIOS'], fw_versions['PD'])
def _ExtractBaseboardAndVersion(factory_branch):
"""Extracts base board and main version from a factory branch name.
By convention, factory branch will be named by 'factory-baseboard-xxxx.B'.
Some factory might have subversions:
factory-baseboard-xxxx.xx.B or factory-baseboard-xxxx.xx.xx.B
Returns: a tuple of (base board, main version) for the factory branch.
For exmaple, returns ('rambi', 5517) for factory-rambi-5517.12.34.B.
"""
if factory_branch in _ExtractBaseboardAndVersion.static_dict:
return _ExtractBaseboardAndVersion.static_dict[factory_branch]
match = re.match(
r'factory-(?:([a-z]+)-)?(\d+)(.\d+)?(.\d+)?.B', factory_branch)
if match is not None:
_ExtractBaseboardAndVersion.static_dict[factory_branch] = (
match.group(1), int(match.group(2)))
else:
_ExtractBaseboardAndVersion.static_dict[factory_branch] = ('Unknown', 0)
return _ExtractBaseboardAndVersion.static_dict[factory_branch]
# Some factory branch names of early projects didn't follow the convention.
# Initializes the static dict with these exceptions.
_ExtractBaseboardAndVersion.static_dict = {
'factory-zako-5220.B': ('beltino', 5220),
'factory-monroe-5140.B': ('beltino', 5140),
'factory-panther-4920.23.B': ('beltino', 4920),
'factory-4455.B': ('slippy', 4455),
'factory-skate-4262.459.B': ('daisy', 4262),
'factory-spring-4262.B': ('daisy', 4262),
}
def GetFactoryBranchInfo(board, base_board, repo_sync, boards_yaml=None):
"""Gets factory branch info.
Gets factory branch info for a board, or all boards based on the base_board,
or all boards if both board and base_board are None.
Args:
board: The board name to get its factory branch info.
base_board: The base board name to get factory branches info for
all boards base on the base_board.
repo_sync: whether to 'repo sync' in platform/chromeos-hwid repo.
boards_yaml: A local yaml file specifying factory branch info for
all boards.
Returns: A list of tuples (board name, factory branch name) sorted by
factory branch version and group by base board.
"""
if boards_yaml:
boards_info = yaml.load(open(boards_yaml))
else:
hwid_dir = os.path.join(
os.environ['CROS_WORKON_SRCROOT'], 'src', 'platform', 'chromeos-hwid')
if not os.path.exists(hwid_dir):
logging.error('No %s in source tree.', hwid_dir)
sys.exit(1)
# Do repo sync -n (fetch only, don't update working tree).
if repo_sync:
process_utils.Spawn(
'repo sync -n .',
log=True, cwd=hwid_dir, shell=True, check_call=True)
# Always read boards.yaml from ToT as all boards are required to have an
# entry in it.
boards_info = yaml.load(process_utils.CheckOutput(
['git', 'show', 'remotes/cros-internal/master:boards.yaml'],
cwd=hwid_dir))
def _LogBaseboardMaxVersion(branch_name, max_version):
base_board, version = _ExtractBaseboardAndVersion(branch_name)
if base_board in max_version and version < max_version[base_board]:
return
else:
max_version[base_board] = version
board_branch_list = []
base_board_max_version = {}
for b in boards_info.itervalues():
# Only get branch info for boards using v3 HWID.
if 'version' in b and b['version'] != 3:
continue
if board:
if b['board'] == board.upper():
return [(b['board'], b['branch'])]
elif base_board:
# Factory branch should contain base board name.
if base_board.lower() in b['branch']:
board_branch_list.append((b['board'], b['branch']))
_LogBaseboardMaxVersion(b['branch'], base_board_max_version)
else:
board_branch_list.append((b['board'], b['branch']))
_LogBaseboardMaxVersion(b['branch'], base_board_max_version)
def _key_func(b):
# Sort by two keys:
# - The max factory version of the base board: group by base board.
# - The factory branch version: list new projects first.
#
# Examples:
# rambi factory-rambi-6420.B.
# squawks factory-rambi-5517.B.
# pi factory-pit-5499.B
base_board, version = _ExtractBaseboardAndVersion(b[1])
return(base_board_max_version[base_board], version)
# Reversely sort the list by the above two keys.
return sorted(board_branch_list, key=_key_func, reverse=True)
def OutputBundleInfo(boards_branch_info, html_file, sync):
"""Print factory bundle information.
Args:
boards_branch_info: A list of tuples, each tuple is (board, factory_branch)
containing the board name and its factory branch name.
html_file: File name to store the output in HTML format.
sync: Whether to sync codebase to get the latest info.
"""
output_lines = [BundleFields]
for board, branch in boards_branch_info:
bundle_info = _GetFactoryBundleInfo(board, branch, sync)
if bundle_info is not None:
output_lines.append(list(bundle_info))
if html_file is None:
num_columns = len(output_lines[0])
# Calculate maximum length of each column.
max_lengths = []
for column_no in xrange(num_columns):
max_lengths.append(max(len(line[column_no]) for line in output_lines))
# Print each line, padding as necessary to the max column length.
for line in output_lines:
for column_no in xrange(num_columns):
sys.stdout.write(line[column_no].ljust(max_lengths[column_no] + 2))
sys.stdout.write('\n')
else:
def _ConvertToHTMLRow(line):
html_row = ''
for item in line:
html_row += '<td>' + item + '</td>'
return '<tr>' + html_row + '</tr>'
html_rows = map(_ConvertToHTMLRow, output_lines)
table_style = ('style="border:2px solid; font-size: 26px" '
'rules="all" cellpadding="5"')
html_table = ('<table ' + table_style + '>\n' +
'\n'.join(html_rows) +
'</table>\n')
footnote = (
'<pre style="font-size: 20px">\n'
'The table is generated from the bundle README files under board '
'private overlay in different factory branches.\n'
'The file "chromeos-base/chromeos-factory-board/files/bundle/README" '
'is automatically generated by "src/platform/factory/bin/'
'finalize_bundle" when a bundle master prepares a factory bundle.\n\n'
'If you found the info is outdated, please update the README file and '
'run script "src/platform/factory-private/sh/update_bundle_info.sh" to '
'regenerate the table.\n'
'</pre>')
file_utils.WriteFile(html_file, html_table + footnote)
def ParseArgs():
parser = argparse.ArgumentParser(
description=DESCRIPTION,
epilog=EXAMPLES,
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--board', '-b',
help='The board name to get factory bundle info.')
parser.add_argument('--base_board', '-bb',
help=('The base board name to get factory bundle info\n'
'for all boards based on the base board.'))
parser.add_argument('--html_file', '-f',
help='File name to store the output in HTML format.')
parser.add_argument('--boards_yaml', '-by',
help=('A local boards.yaml file storing factory branch\n'
'info for all boards. An example of boards.yaml:\n'
'SQUAWKS:\n'
' board: SQUAWKS\n'
' branch: factory-rambi-5517.B\n'
'CANDY:\n'
' board: CANDY\n'
' branch: factory-rambi-6420.B\n'))
parser.add_argument('--no-sync', action='store_false', dest='sync',
help=('Don\'t run repo sync in platform/chromeos-hwid '
'repo and board private overlays.'))
parser.add_argument('--yes', '-y', action='store_true',
help="Don't ask for confirmation to repo sync.")
return parser.parse_args()
def main():
args = ParseArgs()
if not args.yes and args.sync:
answer = raw_input('*** repo sync will be invoked in platform/'
'chromeos-hwid repo and board private overlays.\n'
'*** Continue? [y/N] ')
if not answer or answer[0] not in 'yY':
sys.exit('Aborting.')
boards_branch_info = GetFactoryBranchInfo(
args.board, args.base_board, args.sync, args.boards_yaml)
OutputBundleInfo(boards_branch_info, args.html_file, args.sync)
if __name__ == '__main__':
main()