Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(275)

Side by Side Diff: tools/mb/mb.py

Issue 1342563002: Clean up logging in MB. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: patch for review Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tools/mb/mb_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 """
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
56 help='configuration to analyze') 56 help='configuration to analyze')
57 subp.add_argument('-f', '--config-file', metavar='PATH', 57 subp.add_argument('-f', '--config-file', metavar='PATH',
58 default=self.default_config, 58 default=self.default_config,
59 help='path to config file ' 59 help='path to config file '
60 '(default is //tools/mb/mb_config.pyl)') 60 '(default is //tools/mb/mb_config.pyl)')
61 subp.add_argument('-g', '--goma-dir', default=self.ExpandUser('~/goma'), 61 subp.add_argument('-g', '--goma-dir', default=self.ExpandUser('~/goma'),
62 help='path to goma directory (default is %(default)s).') 62 help='path to goma directory (default is %(default)s).')
63 subp.add_argument('-n', '--dryrun', action='store_true', 63 subp.add_argument('-n', '--dryrun', action='store_true',
64 help='Do a dry run (i.e., do nothing, just print ' 64 help='Do a dry run (i.e., do nothing, just print '
65 'the commands that will run)') 65 'the commands that will run)')
66 subp.add_argument('-q', '--quiet', action='store_true', 66 subp.add_argument('-v', '--verbose', action='count', default=0,
67 help='Do not print anything on success, '
68 'just return an exit code.')
69 subp.add_argument('-v', '--verbose', action='count',
70 help='verbose logging (may specify multiple times).') 67 help='verbose logging (may specify multiple times).')
71 68
72 parser = argparse.ArgumentParser(prog='mb') 69 parser = argparse.ArgumentParser(prog='mb')
73 subps = parser.add_subparsers() 70 subps = parser.add_subparsers()
74 71
75 subp = subps.add_parser('analyze', 72 subp = subps.add_parser('analyze',
76 help='analyze whether changes to a set of files ' 73 help='analyze whether changes to a set of files '
77 'will cause a set of binaries to be rebuilt.') 74 'will cause a set of binaries to be rebuilt.')
78 AddCommonOptions(subp) 75 AddCommonOptions(subp)
79 subp.add_argument('--swarming-targets-file', 76 subp.add_argument('--swarming-targets-file',
(...skipping 24 matching lines...) Expand all
104 'builder') 101 'builder')
105 AddCommonOptions(subp) 102 AddCommonOptions(subp)
106 subp.set_defaults(func=self.CmdLookup) 103 subp.set_defaults(func=self.CmdLookup)
107 104
108 subp = subps.add_parser('validate', 105 subp = subps.add_parser('validate',
109 help='validate the config file') 106 help='validate the config file')
110 subp.add_argument('-f', '--config-file', metavar='PATH', 107 subp.add_argument('-f', '--config-file', metavar='PATH',
111 default=self.default_config, 108 default=self.default_config,
112 help='path to config file ' 109 help='path to config file '
113 '(default is //tools/mb/mb_config.pyl)') 110 '(default is //tools/mb/mb_config.pyl)')
114 subp.add_argument('-q', '--quiet', action='store_true',
115 help='Do not print anything on success, '
116 'just return an exit code.')
117 subp.set_defaults(func=self.CmdValidate) 111 subp.set_defaults(func=self.CmdValidate)
118 112
119 subp = subps.add_parser('help', 113 subp = subps.add_parser('help',
120 help='Get help on a subcommand.') 114 help='Get help on a subcommand.')
121 subp.add_argument(nargs='?', action='store', dest='subcommand', 115 subp.add_argument(nargs='?', action='store', dest='subcommand',
122 help='The command to get help for.') 116 help='The command to get help for.')
123 subp.set_defaults(func=self.CmdHelp) 117 subp.set_defaults(func=self.CmdHelp)
124 118
125 self.args = parser.parse_args(argv) 119 self.args = parser.parse_args(argv)
126 120
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
225 219
226 # Check that every mixin defined is actually referenced somewhere. 220 # Check that every mixin defined is actually referenced somewhere.
227 for mixin in self.mixins: 221 for mixin in self.mixins:
228 if not mixin in referenced_mixins: 222 if not mixin in referenced_mixins:
229 errs.append('Unreferenced mixin "%s".' % mixin) 223 errs.append('Unreferenced mixin "%s".' % mixin)
230 224
231 if errs: 225 if errs:
232 raise MBErr(('mb config file %s has problems:' % self.args.config_file) + 226 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
233 '\n ' + '\n '.join(errs)) 227 '\n ' + '\n '.join(errs))
234 228
235 if not self.args.quiet: 229 self.Print('mb config file %s looks ok.' % self.args.config_file)
236 self.Print('mb config file %s looks ok.' % self.args.config_file)
237 return 0 230 return 0
238 231
239 def GetConfig(self): 232 def GetConfig(self):
240 self.ReadConfigFile() 233 self.ReadConfigFile()
241 config = self.ConfigFromArgs() 234 config = self.ConfigFromArgs()
242 if not config in self.configs: 235 if not config in self.configs:
243 raise MBErr('Config "%s" not found in %s' % 236 raise MBErr('Config "%s" not found in %s' %
244 (config, self.args.config_file)) 237 (config, self.args.config_file))
245 238
246 return self.FlattenConfig(config) 239 return self.FlattenConfig(config)
(...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after
466 ret, _, _ = self.Run(cmd) 459 ret, _, _ = self.Run(cmd)
467 if not ret and self.args.verbose: 460 if not ret and self.args.verbose:
468 outp = json.loads(self.ReadFile(self.args.output_path[0])) 461 outp = json.loads(self.ReadFile(self.args.output_path[0]))
469 self.Print() 462 self.Print()
470 self.Print('analyze output:') 463 self.Print('analyze output:')
471 self.PrintJSON(outp) 464 self.PrintJSON(outp)
472 self.Print() 465 self.Print()
473 466
474 return ret 467 return ret
475 468
476 def RunGNIsolate(self, vals):
477 build_path = self.args.path[0]
478 inp = self.ReadInputJSON(['targets'])
479 if self.args.verbose:
480 self.Print()
481 self.Print('isolate input:')
482 self.PrintJSON(inp)
483 self.Print()
484 output_path = self.args.output_path[0]
485
486 for target in inp['targets']:
487 runtime_deps_path = self.ToAbsPath(build_path, target + '.runtime_deps')
488
489 if not self.Exists(runtime_deps_path):
490 self.WriteFailureAndRaise('"%s" does not exist' % runtime_deps_path,
491 output_path)
492
493 command, extra_files = self.GetIsolateCommand(target, vals, None)
494
495 runtime_deps = self.ReadFile(runtime_deps_path).splitlines()
496
497
498 isolate_path = self.ToAbsPath(build_path, target + '.isolate')
499 self.WriteFile(isolate_path,
500 pprint.pformat({
501 'variables': {
502 'command': command,
503 'files': sorted(runtime_deps + extra_files),
504 }
505 }) + '\n')
506
507 self.WriteJSON(
508 {
509 'args': [
510 '--isolated',
511 self.ToSrcRelPath('%s/%s.isolated' % (build_path, target)),
512 '--isolate',
513 self.ToSrcRelPath('%s/%s.isolate' % (build_path, target)),
514 ],
515 'dir': self.chromium_src_dir,
516 'version': 1,
517 },
518 isolate_path + 'd.gen.json',
519 )
520
521 return 0
522
523 def GetIsolateCommand(self, target, vals, gn_isolate_map): 469 def GetIsolateCommand(self, target, vals, gn_isolate_map):
524 # This needs to mirror the settings in //build/config/ui.gni: 470 # This needs to mirror the settings in //build/config/ui.gni:
525 # use_x11 = is_linux && !use_ozone. 471 # use_x11 = is_linux && !use_ozone.
526 # TODO(dpranke): Figure out how to keep this in sync better. 472 # TODO(dpranke): Figure out how to keep this in sync better.
527 use_x11 = (sys.platform == 'linux2' and 473 use_x11 = (sys.platform == 'linux2' and
528 not 'target_os="android"' in vals['gn_args'] and 474 not 'target_os="android"' in vals['gn_args'] and
529 not 'use_ozone=true' in vals['gn_args']) 475 not 'use_ozone=true' in vals['gn_args'])
530 476
531 asan = 'is_asan=true' in vals['gn_args'] 477 asan = 'is_asan=true' in vals['gn_args']
532 msan = 'is_msan=true' in vals['gn_args'] 478 msan = 'is_msan=true' in vals['gn_args']
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after
676 622
677 ret = 0 623 ret = 0
678 response_file = self.TempFile() 624 response_file = self.TempFile()
679 response_file.write('\n'.join(inp['files']) + '\n') 625 response_file.write('\n'.join(inp['files']) + '\n')
680 response_file.close() 626 response_file.close()
681 627
682 matching_targets = [] 628 matching_targets = []
683 try: 629 try:
684 cmd = self.GNCmd('refs', self.args.path[0]) + [ 630 cmd = self.GNCmd('refs', self.args.path[0]) + [
685 '@%s' % response_file.name, '--all', '--as=output'] 631 '@%s' % response_file.name, '--all', '--as=output']
686 ret, out, _ = self.Run(cmd) 632 ret, out, _ = self.Run(cmd, verbosity=1)
687 if ret and not 'The input matches no targets' in out: 633 if ret and not 'The input matches no targets' in out:
688 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), 634 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out),
689 output_path) 635 output_path)
690 build_dir = self.ToSrcRelPath(self.args.path[0]) + os.sep 636 build_dir = self.ToSrcRelPath(self.args.path[0]) + os.sep
691 for output in out.splitlines(): 637 for output in out.splitlines():
692 build_output = output.replace(build_dir, '') 638 build_output = output.replace(build_dir, '')
693 if build_output in inp['targets']: 639 if build_output in inp['targets']:
694 matching_targets.append(build_output) 640 matching_targets.append(build_output)
695 641
696 cmd = self.GNCmd('refs', self.args.path[0]) + [ 642 cmd = self.GNCmd('refs', self.args.path[0]) + [
697 '@%s' % response_file.name, '--all'] 643 '@%s' % response_file.name, '--all']
698 ret, out, _ = self.Run(cmd) 644 ret, out, _ = self.Run(cmd, verbosity=1)
699 if ret and not 'The input matches no targets' in out: 645 if ret and not 'The input matches no targets' in out:
700 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), 646 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out),
701 output_path) 647 output_path)
702 for label in out.splitlines(): 648 for label in out.splitlines():
703 build_target = label[2:] 649 build_target = label[2:]
704 # We want to accept 'chrome/android:chrome_public_apk' and 650 # We want to accept 'chrome/android:chrome_public_apk' and
705 # just 'chrome_public_apk'. This may result in too many targets 651 # just 'chrome_public_apk'. This may result in too many targets
706 # getting built, but we can adjust that later if need be. 652 # getting built, but we can adjust that later if need be.
707 for input_target in inp['targets']: 653 for input_target in inp['targets']:
708 if (input_target == build_target or 654 if (input_target == build_target or
709 build_target.endswith(':' + input_target)): 655 build_target.endswith(':' + input_target)):
710 matching_targets.append(input_target) 656 matching_targets.append(input_target)
711 finally: 657 finally:
712 self.RemoveFile(response_file.name) 658 self.RemoveFile(response_file.name)
713 659
714 if matching_targets: 660 if matching_targets:
715 # TODO: it could be that a target X might depend on a target Y 661 # TODO: it could be that a target X might depend on a target Y
716 # and both would be listed in the input, but we would only need 662 # and both would be listed in the input, but we would only need
717 # to specify target X as a build_target (whereas both X and Y are 663 # to specify target X as a build_target (whereas both X and Y are
718 # targets). I'm not sure if that optimization is generally worth it. 664 # targets). I'm not sure if that optimization is generally worth it.
719 self.WriteJSON({'targets': sorted(matching_targets), 665 self.WriteJSON({'targets': sorted(set(matching_targets)),
720 'build_targets': sorted(matching_targets), 666 'build_targets': sorted(set(matching_targets)),
Dirk Pranke 2015/09/11 23:27:52 This fixes a minor annoyance where if multiple tar
721 'status': 'Found dependency'}, output_path) 667 'status': 'Found dependency'}, output_path)
722 else: 668 else:
723 self.WriteJSON({'targets': [], 669 self.WriteJSON({'targets': [],
724 'build_targets': [], 670 'build_targets': [],
725 'status': 'No dependency'}, output_path) 671 'status': 'No dependency'}, output_path)
726 672
727 if not ret and self.args.verbose: 673 if self.args.verbose:
Dirk Pranke 2015/09/11 23:27:52 We don't really care about the return value at thi
728 outp = json.loads(self.ReadFile(output_path)) 674 outp = json.loads(self.ReadFile(output_path))
729 self.Print() 675 self.Print()
730 self.Print('analyze output:') 676 self.Print('analyze output:')
731 self.PrintJSON(outp) 677 self.PrintJSON(outp)
732 self.Print() 678 self.Print()
733 679
734 return 0 680 return 0
735 681
736 def ReadInputJSON(self, required_keys): 682 def ReadInputJSON(self, required_keys):
737 path = self.args.input_path[0] 683 path = self.args.input_path[0]
738 output_path = self.args.output_path[0] 684 output_path = self.args.output_path[0]
739 if not self.Exists(path): 685 if not self.Exists(path):
740 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path) 686 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
741 687
742 try: 688 try:
743 inp = json.loads(self.ReadFile(path)) 689 inp = json.loads(self.ReadFile(path))
744 except Exception as e: 690 except Exception as e:
745 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' % 691 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
746 (path, e), output_path) 692 (path, e), output_path)
747 693
748 for k in required_keys: 694 for k in required_keys:
749 if not k in inp: 695 if not k in inp:
750 self.WriteFailureAndRaise('input file is missing a "%s" key' % k, 696 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
751 output_path) 697 output_path)
752 698
753 return inp 699 return inp
754 700
755 def WriteFailureAndRaise(self, msg, output_path): 701 def WriteFailureAndRaise(self, msg, output_path):
756 if output_path: 702 if output_path:
757 self.WriteJSON({'error': msg}, output_path) 703 self.WriteJSON({'error': msg}, output_path, verbosity=0)
758 raise MBErr(msg) 704 raise MBErr(msg)
759 705
760 def WriteJSON(self, obj, path): 706 def WriteJSON(self, obj, path, verbosity=1):
Nico 2015/09/11 23:31:16 add comment explaining what verbosity means (if it
761 try: 707 try:
762 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n') 708 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
709 verbosity=verbosity)
763 except Exception as e: 710 except Exception as e:
764 raise MBErr('Error %s writing to the output path "%s"' % 711 raise MBErr('Error %s writing to the output path "%s"' %
765 (e, path)) 712 (e, path))
766 713
767 def PrintCmd(self, cmd): 714 def PrintCmd(self, cmd):
768 if cmd[0] == sys.executable: 715 if cmd[0] == sys.executable:
769 cmd = ['python'] + cmd[1:] 716 cmd = ['python'] + cmd[1:]
770 self.Print(*[pipes.quote(c) for c in cmd]) 717 self.Print(*[pipes.quote(c) for c in cmd])
771 718
772 def PrintJSON(self, obj): 719 def PrintJSON(self, obj):
773 self.Print(json.dumps(obj, indent=2, sort_keys=True)) 720 self.Print(json.dumps(obj, indent=2, sort_keys=True))
774 721
775 def Print(self, *args, **kwargs): 722 def Print(self, *args, **kwargs):
776 # This function largely exists so it can be overridden for testing. 723 # This function largely exists so it can be overridden for testing.
777 print(*args, **kwargs) 724 print(*args, **kwargs)
778 725
779 def Run(self, cmd, env=None): 726 def Run(self, cmd, env=None, verbosity=0):
780 # This function largely exists so it can be overridden for testing. 727 # This function largely exists so it can be overridden for testing.
781 if self.args.dryrun or self.args.verbose: 728 if self.args.dryrun or self.args.verbose >= verbosity:
782 self.PrintCmd(cmd) 729 self.PrintCmd(cmd)
783 if self.args.dryrun: 730 if self.args.dryrun:
784 return 0, '', '' 731 return 0, '', ''
732
785 ret, out, err = self.Call(cmd, env=env) 733 ret, out, err = self.Call(cmd, env=env)
786 if self.args.verbose: 734 if self.args.verbose >= verbosity:
787 if out: 735 if out:
788 self.Print(out, end='') 736 self.Print(out, end='')
789 if err: 737 if err:
790 self.Print(err, end='', file=sys.stderr) 738 self.Print(err, end='', file=sys.stderr)
791 return ret, out, err 739 return ret, out, err
792 740
793 def Call(self, cmd, env=None): 741 def Call(self, cmd, env=None):
794 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir, 742 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
795 stdout=subprocess.PIPE, stderr=subprocess.PIPE, 743 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
796 env=env) 744 env=env)
(...skipping 21 matching lines...) Expand all
818 return fp.read() 766 return fp.read()
819 767
820 def RemoveFile(self, path): 768 def RemoveFile(self, path):
821 # This function largely exists so it can be overriden for testing. 769 # This function largely exists so it can be overriden for testing.
822 os.remove(path) 770 os.remove(path)
823 771
824 def TempFile(self, mode='w'): 772 def TempFile(self, mode='w'):
825 # This function largely exists so it can be overriden for testing. 773 # This function largely exists so it can be overriden for testing.
826 return tempfile.NamedTemporaryFile(mode=mode, delete=False) 774 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
827 775
828 def WriteFile(self, path, contents): 776 def WriteFile(self, path, contents, verbosity=1):
829 # This function largely exists so it can be overriden for testing. 777 # This function largely exists so it can be overriden for testing.
830 if self.args.dryrun or self.args.verbose: 778 if self.args.dryrun or self.args.verbose >= verbosity:
831 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path)) 779 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
832 with open(path, 'w') as fp: 780 with open(path, 'w') as fp:
833 return fp.write(contents) 781 return fp.write(contents)
834 782
835 783
836 class MBErr(Exception): 784 class MBErr(Exception):
837 pass 785 pass
838 786
839 787
840 if __name__ == '__main__': 788 if __name__ == '__main__':
841 try: 789 try:
842 sys.exit(main(sys.argv[1:])) 790 sys.exit(main(sys.argv[1:]))
843 except MBErr as e: 791 except MBErr as e:
844 print(e) 792 print(e)
845 sys.exit(1) 793 sys.exit(1)
846 except KeyboardInterrupt: 794 except KeyboardInterrupt:
847 print("interrupted, exiting", stream=sys.stderr) 795 print("interrupted, exiting", stream=sys.stderr)
848 sys.exit(130) 796 sys.exit(130)
OLDNEW
« no previous file with comments | « no previous file | tools/mb/mb_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698