| # Copyright (c) 2013 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. |
| |
| """Script for dumping and/or comparing build config contents.""" |
| |
| from __future__ import print_function |
| |
| import json |
| import pprint |
| |
| from chromite.cbuildbot import cbuildbot_config |
| from chromite.lib import commandline |
| from chromite.lib import cros_build_lib |
| |
| |
| class _JSONEncoder(json.JSONEncoder): |
| """Json Encoder that encodes objects as their dictionaries.""" |
| # pylint: disable=E0202 |
| def default(self, obj): |
| return self.encode(obj.__dict__) |
| |
| |
| def _InjectDisplayPosition(config): |
| """Add field to help buildbot masters order builders on the waterfall. |
| |
| Args: |
| config: A dict of build config items. |
| |
| Returns: |
| A similar config where each config item has a new 'display_position' value. |
| """ |
| def _GetSortKey(items): |
| my_config = items[1] |
| # Allow configs to override the display_position. |
| return (my_config.get('display_position', 1000000), |
| cbuildbot_config.GetDisplayPosition(my_config['name']), |
| my_config['internal'], my_config['vm_tests']) |
| |
| source = sorted(config.iteritems(), key=_GetSortKey) |
| return dict((name, dict(value.items() + [('display_position', idx)])) |
| for idx, (name, value) in enumerate(source)) |
| |
| |
| def _DumpConfigJson(cfg): |
| """Dump |cfg| contents in JSON format. |
| |
| Args: |
| cfg: A single build config. |
| """ |
| print(json.dumps(cfg, cls=_JSONEncoder)) |
| |
| |
| def _HideDefaults(cfg): |
| """Hide the defaults from a given config entry. |
| |
| Args: |
| cfg: A config entry. |
| |
| Returns: |
| The same config entry, but without any defaults. |
| """ |
| d = {} |
| for k, v in cfg.iteritems(): |
| if cbuildbot_config.default.get(k) != v: |
| if k == 'child_configs': |
| d[k] = [_HideDefaults(x) for x in v] |
| else: |
| d[k] = v |
| return d |
| |
| |
| def _DumpConfigPrettyJson(cfg): |
| """Dump |cfg| contents in pretty JSON format. |
| |
| Args: |
| cfg: A single build config. |
| """ |
| print(json.dumps(cfg, cls=_JSONEncoder, |
| sort_keys=True, indent=4, separators=(',', ': '))) |
| |
| |
| def _DumpConfigPrettyPrint(cfg): |
| """Dump |cfg| contents in pretty printer format. |
| |
| Args: |
| cfg: A single build config. |
| """ |
| pretty_printer = pprint.PrettyPrinter(indent=2) |
| pretty_printer.pprint(cfg) |
| |
| |
| def _CompareConfig(old_cfg, new_cfg): |
| """Compare two build configs targets, printing results. |
| |
| Args: |
| old_cfg: The 'from' build config for comparison. |
| new_cfg: The 'to' build config for comparison. |
| """ |
| new_cfg = json.loads(json.dumps(new_cfg, cls=_JSONEncoder)) |
| for key in sorted(set(new_cfg.keys() + old_cfg.keys())): |
| obj1, obj2 = old_cfg.get(key), new_cfg.get(key) |
| if obj1 == obj2: |
| continue |
| elif obj1 is None: |
| print('%s: added to config\n' % (key,)) |
| continue |
| elif obj2 is None: |
| print('%s: removed from config\n' % (key,)) |
| continue |
| |
| print('%s:' % (key,)) |
| |
| for subkey in sorted(set(obj1.keys() + obj2.keys())): |
| sobj1, sobj2 = obj1.get(subkey), obj2.get(subkey) |
| if sobj1 != sobj2: |
| print(' %s: %r, %r' % (subkey, sobj1, sobj2)) |
| |
| print() |
| |
| |
| def GetParser(): |
| """Creates the argparse parser.""" |
| parser = commandline.ArgumentParser(description=__doc__) |
| |
| # Put options that control the mode of script into mutually exclusive group. |
| mode = parser.add_mutually_exclusive_group(required=True) |
| mode.add_argument('-c', '--compare', action='store', |
| type=commandline.argparse.FileType('rb'), |
| default=None, metavar='file_name', |
| help='Compare current config against a saved on disk ' |
| 'serialized (json) dump of a config.') |
| mode.add_argument('-d', '--dump', action='store_true', default=False, |
| help='Dump the configs in JSON format.') |
| |
| parser.add_argument('--pretty', action='store_true', default=False, |
| help='If dumping, make json output human readable.') |
| parser.add_argument('--for-buildbot', action='store_true', default=False, |
| help='Include the display position in data.') |
| parser.add_argument('-s', '--separate-defaults', action='store_true', |
| default=False, help='Show the defaults separately.') |
| parser.add_argument('config_targets', metavar='config_target', nargs='*', |
| help='Name of a cbuildbot config target.') |
| |
| return parser |
| |
| |
| def main(argv): |
| parser = GetParser() |
| options = parser.parse_args(argv) |
| |
| if options.pretty and not options.dump: |
| parser.error('The --pretty option does not make sense without --dump') |
| |
| # Possibly translate config contents first. |
| convert = lambda x: x |
| if options.for_buildbot: |
| convert = _InjectDisplayPosition |
| |
| config = convert(cbuildbot_config.config) |
| |
| # Separate the defaults and show them at the top. We prefix the name with |
| # an underscore so that it sorts to the top. |
| if options.separate_defaults: |
| for k, v in config.iteritems(): |
| config[k] = _HideDefaults(v) |
| config['_default'] = cbuildbot_config.default |
| |
| # If config_targets specified, only dump/load those. |
| if options.config_targets: |
| temp_config = dict() |
| for c in options.config_targets: |
| try: |
| temp_config[c] = config[c] |
| except KeyError: |
| cros_build_lib.Die('No such config id: %s', c) |
| |
| config = temp_config |
| |
| if config: |
| if options.dump: |
| if options.pretty: |
| _DumpConfigPrettyJson(config) |
| else: |
| _DumpConfigJson(config) |
| elif options.compare: |
| # Load the previously saved build config for comparison. |
| old_cfg = convert(json.load(options.compare)) |
| _CompareConfig(old_cfg, config) |
| |
| return 0 |