Chromium Code Reviews| 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 """ |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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) |
| OLD | NEW |