| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2015 The Chromium Authors. All rights reserved. | 2 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """MB - the Meta-Build wrapper around GYP and GN | 6 """MB - the Meta-Build wrapper around GYP and GN |
| 7 | 7 |
| 8 MB is a wrapper script for GYP and GN that can be used to generate build files | 8 MB is a wrapper script for GYP and GN that can be used to generate build files |
| 9 for sets of canned configurations and analyze them. | 9 for sets of canned configurations and analyze them. |
| 10 """ | 10 """ |
| 11 | 11 |
| 12 from __future__ import print_function | 12 from __future__ import print_function |
| 13 | 13 |
| 14 import argparse | 14 import argparse |
| 15 import ast | 15 import ast |
| 16 import errno | 16 import errno |
| 17 import json | 17 import json |
| 18 import os | 18 import os |
| 19 import pipes | 19 import pipes |
| 20 import pprint | 20 import pprint |
| 21 import re | 21 import re |
| 22 import shutil | 22 import shutil |
| 23 import sys | 23 import sys |
| 24 import subprocess | 24 import subprocess |
| 25 import tempfile | 25 import tempfile |
| 26 import urllib2 |
| 27 |
| 28 from collections import OrderedDict |
| 26 | 29 |
| 27 def main(args): | 30 def main(args): |
| 28 mbw = MetaBuildWrapper() | 31 mbw = MetaBuildWrapper() |
| 29 mbw.ParseArgs(args) | 32 mbw.ParseArgs(args) |
| 30 | 33 |
| 31 try: | 34 try: |
| 32 ret = mbw.args.func() | 35 ret = mbw.args.func() |
| 33 if ret: | 36 if ret: |
| 34 mbw.DumpInputFiles() | 37 mbw.DumpInputFiles() |
| 35 return ret | 38 return ret |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 135 subp.set_defaults(func=self.CmdRun) | 138 subp.set_defaults(func=self.CmdRun) |
| 136 | 139 |
| 137 subp = subps.add_parser('validate', | 140 subp = subps.add_parser('validate', |
| 138 help='validate the config file') | 141 help='validate the config file') |
| 139 subp.add_argument('-f', '--config-file', metavar='PATH', | 142 subp.add_argument('-f', '--config-file', metavar='PATH', |
| 140 default=self.default_config, | 143 default=self.default_config, |
| 141 help='path to config file ' | 144 help='path to config file ' |
| 142 '(default is //tools/mb/mb_config.pyl)') | 145 '(default is //tools/mb/mb_config.pyl)') |
| 143 subp.set_defaults(func=self.CmdValidate) | 146 subp.set_defaults(func=self.CmdValidate) |
| 144 | 147 |
| 148 subp = subps.add_parser('audit', |
| 149 help='Audit the config file to track progress') |
| 150 subp.add_argument('-f', '--config-file', metavar='PATH', |
| 151 default=self.default_config, |
| 152 help='path to config file ' |
| 153 '(default is //tools/mb/mb_config.pyl)') |
| 154 subp.add_argument('-i', '--internal', action='store_true', |
| 155 help='check internal masters also') |
| 156 subp.add_argument('-m', '--master', action='append', |
| 157 help='master to audit (default is all non-internal ' |
| 158 'masters in file)') |
| 159 subp.add_argument('-u', '--url-template', action='store', |
| 160 default='https://build.chromium.org/p/' |
| 161 '{master}/json/builders', |
| 162 help='URL scheme for JSON APIs to buildbot ' |
| 163 '(default: %(default)s) ') |
| 164 subp.set_defaults(func=self.CmdAudit) |
| 165 |
| 145 subp = subps.add_parser('help', | 166 subp = subps.add_parser('help', |
| 146 help='Get help on a subcommand.') | 167 help='Get help on a subcommand.') |
| 147 subp.add_argument(nargs='?', action='store', dest='subcommand', | 168 subp.add_argument(nargs='?', action='store', dest='subcommand', |
| 148 help='The command to get help for.') | 169 help='The command to get help for.') |
| 149 subp.set_defaults(func=self.CmdHelp) | 170 subp.set_defaults(func=self.CmdHelp) |
| 150 | 171 |
| 151 self.args = parser.parse_args(argv) | 172 self.args = parser.parse_args(argv) |
| 152 | 173 |
| 153 def DumpInputFiles(self): | 174 def DumpInputFiles(self): |
| 154 | 175 |
| (...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 327 for mixin in self.configs[config]: | 348 for mixin in self.configs[config]: |
| 328 RecurseMixins(mixin) | 349 RecurseMixins(mixin) |
| 329 | 350 |
| 330 if errs: | 351 if errs: |
| 331 raise MBErr(('mb config file %s has problems:' % self.args.config_file) + | 352 raise MBErr(('mb config file %s has problems:' % self.args.config_file) + |
| 332 '\n ' + '\n '.join(errs)) | 353 '\n ' + '\n '.join(errs)) |
| 333 | 354 |
| 334 self.Print('mb config file %s looks ok.' % self.args.config_file) | 355 self.Print('mb config file %s looks ok.' % self.args.config_file) |
| 335 return 0 | 356 return 0 |
| 336 | 357 |
| 358 def CmdAudit(self): |
| 359 """Track the progress of the GYP->GN migration on the bots.""" |
| 360 |
| 361 stats = OrderedDict() |
| 362 STAT_MASTER_ONLY = 'Master only' |
| 363 STAT_CONFIG_ONLY = 'Config only' |
| 364 STAT_TBD = 'Still TBD' |
| 365 STAT_GYP = 'Still GYP' |
| 366 STAT_DONE = 'Done (on GN)' |
| 367 stats[STAT_MASTER_ONLY] = 0 |
| 368 stats[STAT_CONFIG_ONLY] = 0 |
| 369 stats[STAT_TBD] = 0 |
| 370 stats[STAT_GYP] = 0 |
| 371 stats[STAT_DONE] = 0 |
| 372 |
| 373 def PrintBuilders(heading, builders): |
| 374 stats.setdefault(heading, 0) |
| 375 stats[heading] += len(builders) |
| 376 if builders: |
| 377 self.Print(' %s:' % heading) |
| 378 for builder in sorted(builders): |
| 379 self.Print(' %s' % builder) |
| 380 |
| 381 self.ReadConfigFile() |
| 382 |
| 383 masters = self.args.master or self.masters |
| 384 for master in sorted(masters): |
| 385 url = self.args.url_template.replace('{master}', master) |
| 386 |
| 387 self.Print('Auditing %s' % master) |
| 388 |
| 389 MASTERS_TO_SKIP = ( |
| 390 'client.skia', |
| 391 'client.v8.fyi', |
| 392 'tryserver.v8', |
| 393 ) |
| 394 if master in MASTERS_TO_SKIP: |
| 395 # Skip these bots because converting them is the responsibility of |
| 396 # those teams and out of scope for the Chromium migration to GN. |
| 397 self.Print(' Skipped (out of scope)') |
| 398 self.Print('') |
| 399 continue |
| 400 |
| 401 INTERNAL_MASTERS = ( |
| 402 'chrome', |
| 403 'chrome.continuous', |
| 404 'official.desktop', |
| 405 'official.desktop.continuous', |
| 406 ) |
| 407 if master in INTERNAL_MASTERS and not self.args.internal: |
| 408 # Skip these because the servers aren't accessible by default ... |
| 409 self.Print(' Skipped (internal)') |
| 410 self.Print('') |
| 411 continue |
| 412 |
| 413 try: |
| 414 # Fetch the /builders contents from the buildbot master. The |
| 415 # keys of the dict are the builder names themselves. |
| 416 json_contents = self.Fetch(url) |
| 417 d = json.loads(json_contents) |
| 418 except Exception as e: |
| 419 self.Print(str(e)) |
| 420 return 1 |
| 421 |
| 422 config_builders = set(self.masters[master]) |
| 423 master_builders = set(d.keys()) |
| 424 both = master_builders & config_builders |
| 425 master_only = master_builders - config_builders |
| 426 config_only = config_builders - master_builders |
| 427 tbd = set() |
| 428 gyp = set() |
| 429 done = set() |
| 430 |
| 431 for builder in both: |
| 432 config = self.masters[master][builder] |
| 433 if config == 'tbd': |
| 434 tbd.add(builder) |
| 435 else: |
| 436 # TODO(dpranke): Check if MB is actually running? |
| 437 vals = self.FlattenConfig(config) |
| 438 if vals['type'] == 'gyp': |
| 439 gyp.add(builder) |
| 440 else: |
| 441 done.add(builder) |
| 442 |
| 443 if master_only or config_only or tbd or gyp: |
| 444 PrintBuilders(STAT_MASTER_ONLY, master_only) |
| 445 PrintBuilders(STAT_CONFIG_ONLY, config_only) |
| 446 PrintBuilders(STAT_TBD, tbd) |
| 447 PrintBuilders(STAT_GYP, gyp) |
| 448 else: |
| 449 self.Print(' ... ok') |
| 450 |
| 451 stats[STAT_DONE] += len(done) |
| 452 |
| 453 self.Print('') |
| 454 |
| 455 fmt = '{:<27} {:>4}' |
| 456 self.Print(fmt.format('Totals', str(sum(int(v) for v in stats.values())))) |
| 457 self.Print(fmt.format('-' * 27, '----')) |
| 458 for stat, count in stats.items(): |
| 459 self.Print(fmt.format(stat, str(count))) |
| 460 |
| 461 return 0 |
| 462 |
| 337 def GetConfig(self): | 463 def GetConfig(self): |
| 338 build_dir = self.args.path[0] | 464 build_dir = self.args.path[0] |
| 339 | 465 |
| 340 vals = {} | 466 vals = {} |
| 341 if self.args.builder or self.args.master or self.args.config: | 467 if self.args.builder or self.args.master or self.args.config: |
| 342 vals = self.Lookup() | 468 vals = self.Lookup() |
| 343 if vals['type'] == 'gn': | 469 if vals['type'] == 'gn': |
| 344 # Re-run gn gen in order to ensure the config is consistent with the | 470 # Re-run gn gen in order to ensure the config is consistent with the |
| 345 # build dir. | 471 # build dir. |
| 346 self.RunGNGen(vals) | 472 self.RunGNGen(vals) |
| (...skipping 742 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1089 return p.returncode, out, err | 1215 return p.returncode, out, err |
| 1090 | 1216 |
| 1091 def ExpandUser(self, path): | 1217 def ExpandUser(self, path): |
| 1092 # This function largely exists so it can be overridden for testing. | 1218 # This function largely exists so it can be overridden for testing. |
| 1093 return os.path.expanduser(path) | 1219 return os.path.expanduser(path) |
| 1094 | 1220 |
| 1095 def Exists(self, path): | 1221 def Exists(self, path): |
| 1096 # This function largely exists so it can be overridden for testing. | 1222 # This function largely exists so it can be overridden for testing. |
| 1097 return os.path.exists(path) | 1223 return os.path.exists(path) |
| 1098 | 1224 |
| 1225 def Fetch(self, url): |
| 1226 |
| 1227 f = urllib2.urlopen(url) |
| 1228 contents = f.read() |
| 1229 f.close() |
| 1230 return contents |
| 1231 |
| 1099 def GNTargetName(self, target): | 1232 def GNTargetName(self, target): |
| 1100 return target[:-len('_apk')] if target.endswith('_apk') else target | 1233 return target[:-len('_apk')] if target.endswith('_apk') else target |
| 1101 | 1234 |
| 1102 def MaybeMakeDirectory(self, path): | 1235 def MaybeMakeDirectory(self, path): |
| 1103 try: | 1236 try: |
| 1104 os.makedirs(path) | 1237 os.makedirs(path) |
| 1105 except OSError, e: | 1238 except OSError, e: |
| 1106 if e.errno != errno.EEXIST: | 1239 if e.errno != errno.EEXIST: |
| 1107 raise | 1240 raise |
| 1108 | 1241 |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1177 | 1310 |
| 1178 if __name__ == '__main__': | 1311 if __name__ == '__main__': |
| 1179 try: | 1312 try: |
| 1180 sys.exit(main(sys.argv[1:])) | 1313 sys.exit(main(sys.argv[1:])) |
| 1181 except MBErr as e: | 1314 except MBErr as e: |
| 1182 print(e) | 1315 print(e) |
| 1183 sys.exit(1) | 1316 sys.exit(1) |
| 1184 except KeyboardInterrupt: | 1317 except KeyboardInterrupt: |
| 1185 print("interrupted, exiting", stream=sys.stderr) | 1318 print("interrupted, exiting", stream=sys.stderr) |
| 1186 sys.exit(130) | 1319 sys.exit(130) |
| OLD | NEW |