| Index: tools/mb/mb.py
|
| diff --git a/tools/mb/mb.py b/tools/mb/mb.py
|
| index 09c8ebcd833177a8ec97f612a55787ad7966bcb9..12617a31bd1ce924ea1bca59bacbfc3923a2dcba 100755
|
| --- a/tools/mb/mb.py
|
| +++ b/tools/mb/mb.py
|
| @@ -23,6 +23,9 @@ import shutil
|
| import sys
|
| import subprocess
|
| import tempfile
|
| +import urllib2
|
| +
|
| +from collections import OrderedDict
|
|
|
| def main(args):
|
| mbw = MetaBuildWrapper()
|
| @@ -142,6 +145,24 @@ class MetaBuildWrapper(object):
|
| '(default is //tools/mb/mb_config.pyl)')
|
| subp.set_defaults(func=self.CmdValidate)
|
|
|
| + subp = subps.add_parser('audit',
|
| + help='Audit the config file to track progress')
|
| + subp.add_argument('-f', '--config-file', metavar='PATH',
|
| + default=self.default_config,
|
| + help='path to config file '
|
| + '(default is //tools/mb/mb_config.pyl)')
|
| + subp.add_argument('-i', '--internal', action='store_true',
|
| + help='check internal masters also')
|
| + subp.add_argument('-m', '--master', action='append',
|
| + help='master to audit (default is all non-internal '
|
| + 'masters in file)')
|
| + subp.add_argument('-u', '--url-template', action='store',
|
| + default='https://build.chromium.org/p/'
|
| + '{master}/json/builders',
|
| + help='URL scheme for JSON APIs to buildbot '
|
| + '(default: %(default)s) ')
|
| + subp.set_defaults(func=self.CmdAudit)
|
| +
|
| subp = subps.add_parser('help',
|
| help='Get help on a subcommand.')
|
| subp.add_argument(nargs='?', action='store', dest='subcommand',
|
| @@ -334,6 +355,111 @@ class MetaBuildWrapper(object):
|
| self.Print('mb config file %s looks ok.' % self.args.config_file)
|
| return 0
|
|
|
| + def CmdAudit(self):
|
| + """Track the progress of the GYP->GN migration on the bots."""
|
| +
|
| + stats = OrderedDict()
|
| + STAT_MASTER_ONLY = 'Master only'
|
| + STAT_CONFIG_ONLY = 'Config only'
|
| + STAT_TBD = 'Still TBD'
|
| + STAT_GYP = 'Still GYP'
|
| + STAT_DONE = 'Done (on GN)'
|
| + stats[STAT_MASTER_ONLY] = 0
|
| + stats[STAT_CONFIG_ONLY] = 0
|
| + stats[STAT_TBD] = 0
|
| + stats[STAT_GYP] = 0
|
| + stats[STAT_DONE] = 0
|
| +
|
| + def PrintBuilders(heading, builders):
|
| + stats.setdefault(heading, 0)
|
| + stats[heading] += len(builders)
|
| + if builders:
|
| + self.Print(' %s:' % heading)
|
| + for builder in sorted(builders):
|
| + self.Print(' %s' % builder)
|
| +
|
| + self.ReadConfigFile()
|
| +
|
| + masters = self.args.master or self.masters
|
| + for master in sorted(masters):
|
| + url = self.args.url_template.replace('{master}', master)
|
| +
|
| + self.Print('Auditing %s' % master)
|
| +
|
| + MASTERS_TO_SKIP = (
|
| + 'client.skia',
|
| + 'client.v8.fyi',
|
| + 'tryserver.v8',
|
| + )
|
| + if master in MASTERS_TO_SKIP:
|
| + # Skip these bots because converting them is the responsibility of
|
| + # those teams and out of scope for the Chromium migration to GN.
|
| + self.Print(' Skipped (out of scope)')
|
| + self.Print('')
|
| + continue
|
| +
|
| + INTERNAL_MASTERS = (
|
| + 'chrome',
|
| + 'chrome.continuous',
|
| + 'official.desktop',
|
| + 'official.desktop.continuous',
|
| + )
|
| + if master in INTERNAL_MASTERS and not self.args.internal:
|
| + # Skip these because the servers aren't accessible by default ...
|
| + self.Print(' Skipped (internal)')
|
| + self.Print('')
|
| + continue
|
| +
|
| + try:
|
| + # Fetch the /builders contents from the buildbot master. The
|
| + # keys of the dict are the builder names themselves.
|
| + json_contents = self.Fetch(url)
|
| + d = json.loads(json_contents)
|
| + except Exception as e:
|
| + self.Print(str(e))
|
| + return 1
|
| +
|
| + config_builders = set(self.masters[master])
|
| + master_builders = set(d.keys())
|
| + both = master_builders & config_builders
|
| + master_only = master_builders - config_builders
|
| + config_only = config_builders - master_builders
|
| + tbd = set()
|
| + gyp = set()
|
| + done = set()
|
| +
|
| + for builder in both:
|
| + config = self.masters[master][builder]
|
| + if config == 'tbd':
|
| + tbd.add(builder)
|
| + else:
|
| + # TODO(dpranke): Check if MB is actually running?
|
| + vals = self.FlattenConfig(config)
|
| + if vals['type'] == 'gyp':
|
| + gyp.add(builder)
|
| + else:
|
| + done.add(builder)
|
| +
|
| + if master_only or config_only or tbd or gyp:
|
| + PrintBuilders(STAT_MASTER_ONLY, master_only)
|
| + PrintBuilders(STAT_CONFIG_ONLY, config_only)
|
| + PrintBuilders(STAT_TBD, tbd)
|
| + PrintBuilders(STAT_GYP, gyp)
|
| + else:
|
| + self.Print(' ... ok')
|
| +
|
| + stats[STAT_DONE] += len(done)
|
| +
|
| + self.Print('')
|
| +
|
| + fmt = '{:<27} {:>4}'
|
| + self.Print(fmt.format('Totals', str(sum(int(v) for v in stats.values()))))
|
| + self.Print(fmt.format('-' * 27, '----'))
|
| + for stat, count in stats.items():
|
| + self.Print(fmt.format(stat, str(count)))
|
| +
|
| + return 0
|
| +
|
| def GetConfig(self):
|
| build_dir = self.args.path[0]
|
|
|
| @@ -1096,6 +1222,13 @@ class MetaBuildWrapper(object):
|
| # This function largely exists so it can be overridden for testing.
|
| return os.path.exists(path)
|
|
|
| + def Fetch(self, url):
|
| +
|
| + f = urllib2.urlopen(url)
|
| + contents = f.read()
|
| + f.close()
|
| + return contents
|
| +
|
| def GNTargetName(self, target):
|
| return target[:-len('_apk')] if target.endswith('_apk') else target
|
|
|
|
|