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 self.ReadConfigFile() | |
360 | |
361 STAT_MISSING_ON_MASTER = 'in config but not on master' | |
362 STAT_MISSING_IN_CONFIG = 'on master but not in config' | |
363 STAT_TBD = 'still TBD' | |
364 STAT_GYP = 'still on GYP' | |
365 STAT_DONE = 'done (on GN)' | |
366 stats = OrderedDict() | |
367 stats[STAT_MISSING_ON_MASTER] = 0 | |
368 stats[STAT_MISSING_IN_CONFIG] = 0 | |
369 stats[STAT_TBD] = 0 | |
370 stats[STAT_GYP] = 0 | |
371 stats[STAT_DONE] = 0 | |
372 | |
373 masters = self.args.master or self.masters | |
374 for master in sorted(masters): | |
375 config_builders = sorted(self.masters[master]) | |
376 url = self.args.url_template.replace('{master}', master) | |
377 | |
378 self.Print('Auditing %s' % master) | |
379 | |
380 MASTERS_TO_SKIP = ( | |
381 'client.skia', | |
382 'client.v8.fyi', | |
383 'tryserver.v8', | |
384 ) | |
385 if master in MASTERS_TO_SKIP: | |
386 # We skip these bots because we don't care if they're converted. | |
387 self.Print(' skipped (do not care if incomplete)') | |
388 self.Print('') | |
389 continue | |
390 | |
391 INTERNAL_MASTERS = ( | |
392 'chrome', | |
393 'chrome.continuous', | |
394 'official.desktop', | |
395 'official.desktop.continuous' | |
396 ) | |
397 if master in INTERNAL_MASTERS and not self.args.internal: | |
398 self.Print(' skipped (internal)') | |
399 self.Print('') | |
400 continue | |
401 | |
402 try: | |
403 json_contents = self.Fetch(url) | |
404 d = json.loads(json_contents) | |
405 except Exception as e: | |
406 self.Print(str(e)) | |
407 return 1 | |
408 | |
409 missing_on_master = [] | |
410 missing_in_config = [] | |
411 tbd = [] | |
412 gyp = [] | |
413 done = [] | |
414 buildbot_builders = sorted(d.keys()) | |
415 for builder in config_builders: | |
416 if builder in buildbot_builders: | |
417 config = self.masters[master][builder] | |
418 if config == 'tbd': | |
419 tbd.append(builder) | |
420 else: | |
421 # TODO(dpranke): Check if MB is actually running? | |
422 vals = self.FlattenConfig(config) | |
423 if vals['type'] == 'gyp': | |
424 gyp.append(builder) | |
425 else: | |
426 done.append(builder) | |
427 else: | |
428 missing_on_master.append(builder) | |
429 | |
430 for builder in buildbot_builders: | |
estaab
2016/03/12 04:07:27
nit:
missing_in_config = sorted(set(buildbot_build
| |
431 if builder not in config_builders: | |
432 missing_in_config.append(builder) | |
433 | |
434 def PrintBuilders(heading, builders): | |
435 stats.setdefault(heading, 0) | |
436 stats[heading] += len(builders) | |
437 if builders: | |
438 self.Print(' %s:' % heading) | |
439 for builder in builders: | |
440 self.Print(' %s' % builder) | |
441 | |
442 if missing_in_config or missing_on_master or tbd or gyp: | |
443 PrintBuilders(STAT_MISSING_IN_CONFIG, missing_in_config) | |
444 PrintBuilders(STAT_MISSING_ON_MASTER, missing_on_master) | |
445 PrintBuilders(STAT_TBD, tbd) | |
446 PrintBuilders(STAT_GYP, gyp) | |
447 else: | |
448 self.Print(' ... ok') | |
449 | |
450 stats[STAT_DONE] += len(done) | |
451 | |
452 self.Print('') | |
453 | |
454 fmt = '{:<27} {:>4}' | |
455 self.Print(fmt.format('Totals', str(sum(int(v) for v in stats.values())))) | |
456 self.Print(fmt.format('-' * 27, '----')) | |
457 for stat, count in stats.items(): | |
458 self.Print(fmt.format(stat, str(count))) | |
459 return 0 | |
460 | |
337 def GetConfig(self): | 461 def GetConfig(self): |
338 build_dir = self.args.path[0] | 462 build_dir = self.args.path[0] |
339 | 463 |
340 vals = {} | 464 vals = {} |
341 if self.args.builder or self.args.master or self.args.config: | 465 if self.args.builder or self.args.master or self.args.config: |
342 vals = self.Lookup() | 466 vals = self.Lookup() |
343 if vals['type'] == 'gn': | 467 if vals['type'] == 'gn': |
344 # Re-run gn gen in order to ensure the config is consistent with the | 468 # Re-run gn gen in order to ensure the config is consistent with the |
345 # build dir. | 469 # build dir. |
346 self.RunGNGen(vals) | 470 self.RunGNGen(vals) |
(...skipping 742 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1089 return p.returncode, out, err | 1213 return p.returncode, out, err |
1090 | 1214 |
1091 def ExpandUser(self, path): | 1215 def ExpandUser(self, path): |
1092 # This function largely exists so it can be overridden for testing. | 1216 # This function largely exists so it can be overridden for testing. |
1093 return os.path.expanduser(path) | 1217 return os.path.expanduser(path) |
1094 | 1218 |
1095 def Exists(self, path): | 1219 def Exists(self, path): |
1096 # This function largely exists so it can be overridden for testing. | 1220 # This function largely exists so it can be overridden for testing. |
1097 return os.path.exists(path) | 1221 return os.path.exists(path) |
1098 | 1222 |
1223 def Fetch(self, url): | |
1224 | |
1225 f = urllib2.urlopen(url) | |
1226 contents = f.read() | |
1227 f.close() | |
1228 return contents | |
1229 | |
1099 def GNTargetName(self, target): | 1230 def GNTargetName(self, target): |
1100 return target[:-len('_apk')] if target.endswith('_apk') else target | 1231 return target[:-len('_apk')] if target.endswith('_apk') else target |
1101 | 1232 |
1102 def MaybeMakeDirectory(self, path): | 1233 def MaybeMakeDirectory(self, path): |
1103 try: | 1234 try: |
1104 os.makedirs(path) | 1235 os.makedirs(path) |
1105 except OSError, e: | 1236 except OSError, e: |
1106 if e.errno != errno.EEXIST: | 1237 if e.errno != errno.EEXIST: |
1107 raise | 1238 raise |
1108 | 1239 |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1177 | 1308 |
1178 if __name__ == '__main__': | 1309 if __name__ == '__main__': |
1179 try: | 1310 try: |
1180 sys.exit(main(sys.argv[1:])) | 1311 sys.exit(main(sys.argv[1:])) |
1181 except MBErr as e: | 1312 except MBErr as e: |
1182 print(e) | 1313 print(e) |
1183 sys.exit(1) | 1314 sys.exit(1) |
1184 except KeyboardInterrupt: | 1315 except KeyboardInterrupt: |
1185 print("interrupted, exiting", stream=sys.stderr) | 1316 print("interrupted, exiting", stream=sys.stderr) |
1186 sys.exit(130) | 1317 sys.exit(130) |
OLD | NEW |