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 |