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='store_true', |
67 help='Do not print anything on success, ' | 67 help='verbose logging') |
68 'just return an exit code.') | |
69 subp.add_argument('-v', '--verbose', action='count', | |
70 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', |
80 help='save runtime dependencies for targets listed ' | 77 help='save runtime dependencies for targets listed ' |
(...skipping 23 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 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
352 | 345 |
353 gn_runtime_deps_path = self.ToAbsPath(path, 'runtime_deps') | 346 gn_runtime_deps_path = self.ToAbsPath(path, 'runtime_deps') |
354 | 347 |
355 # Since GN hasn't run yet, the build directory may not even exist. | 348 # Since GN hasn't run yet, the build directory may not even exist. |
356 self.MaybeMakeDirectory(self.ToAbsPath(path)) | 349 self.MaybeMakeDirectory(self.ToAbsPath(path)) |
357 | 350 |
358 self.WriteFile(gn_runtime_deps_path, '\n'.join(gn_labels) + '\n') | 351 self.WriteFile(gn_runtime_deps_path, '\n'.join(gn_labels) + '\n') |
359 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path) | 352 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path) |
360 | 353 |
361 ret, _, _ = self.Run(cmd) | 354 ret, _, _ = self.Run(cmd) |
| 355 if ret: |
| 356 # If `gn gen` failed, we should exit early rather than trying to |
| 357 # generate isolates. Run() will have already logged any error output. |
| 358 self.Print('GN gen failed: %d' % ret) |
| 359 return ret |
362 | 360 |
363 for target in swarming_targets: | 361 for target in swarming_targets: |
364 if gn_isolate_map[target]['type'] == 'gpu_browser_test': | 362 if gn_isolate_map[target]['type'] == 'gpu_browser_test': |
365 runtime_deps_target = 'browser_tests' | 363 runtime_deps_target = 'browser_tests' |
366 elif gn_isolate_map[target]['type'] == 'script': | 364 elif gn_isolate_map[target]['type'] == 'script': |
367 # For script targets, the build target is usually a group, | 365 # For script targets, the build target is usually a group, |
368 # for which gn generates the runtime_deps next to the stamp file | 366 # for which gn generates the runtime_deps next to the stamp file |
369 # for the label, which lives under the obj/ directory. | 367 # for the label, which lives under the obj/ directory. |
370 label = gn_isolate_map[target]['label'] | 368 label = gn_isolate_map[target]['label'] |
371 runtime_deps_target = 'obj/%s.stamp' % label.replace(':', '/') | 369 runtime_deps_target = 'obj/%s.stamp' % label.replace(':', '/') |
(...skipping 29 matching lines...) Expand all Loading... |
401 self.ToSrcRelPath('%s%s%s.isolated' % (path, os.sep, target)), | 399 self.ToSrcRelPath('%s%s%s.isolated' % (path, os.sep, target)), |
402 '--isolate', | 400 '--isolate', |
403 self.ToSrcRelPath('%s%s%s.isolate' % (path, os.sep, target)), | 401 self.ToSrcRelPath('%s%s%s.isolate' % (path, os.sep, target)), |
404 ], | 402 ], |
405 'dir': self.chromium_src_dir, | 403 'dir': self.chromium_src_dir, |
406 'version': 1, | 404 'version': 1, |
407 }, | 405 }, |
408 isolate_path + 'd.gen.json', | 406 isolate_path + 'd.gen.json', |
409 ) | 407 ) |
410 | 408 |
411 | |
412 return ret | 409 return ret |
413 | 410 |
414 def GNCmd(self, subcommand, path, gn_args=''): | 411 def GNCmd(self, subcommand, path, gn_args=''): |
415 if self.platform == 'linux2': | 412 if self.platform == 'linux2': |
416 gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'linux64', | 413 gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'linux64', |
417 'gn') | 414 'gn') |
418 elif self.platform == 'darwin': | 415 elif self.platform == 'darwin': |
419 gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'mac', | 416 gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'mac', |
420 'gn') | 417 'gn') |
421 else: | 418 else: |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
466 ret, _, _ = self.Run(cmd) | 463 ret, _, _ = self.Run(cmd) |
467 if not ret and self.args.verbose: | 464 if not ret and self.args.verbose: |
468 outp = json.loads(self.ReadFile(self.args.output_path[0])) | 465 outp = json.loads(self.ReadFile(self.args.output_path[0])) |
469 self.Print() | 466 self.Print() |
470 self.Print('analyze output:') | 467 self.Print('analyze output:') |
471 self.PrintJSON(outp) | 468 self.PrintJSON(outp) |
472 self.Print() | 469 self.Print() |
473 | 470 |
474 return ret | 471 return ret |
475 | 472 |
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): | 473 def GetIsolateCommand(self, target, vals, gn_isolate_map): |
524 # This needs to mirror the settings in //build/config/ui.gni: | 474 # This needs to mirror the settings in //build/config/ui.gni: |
525 # use_x11 = is_linux && !use_ozone. | 475 # use_x11 = is_linux && !use_ozone. |
526 # TODO(dpranke): Figure out how to keep this in sync better. | 476 # TODO(dpranke): Figure out how to keep this in sync better. |
527 use_x11 = (sys.platform == 'linux2' and | 477 use_x11 = (sys.platform == 'linux2' and |
528 not 'target_os="android"' in vals['gn_args'] and | 478 not 'target_os="android"' in vals['gn_args'] and |
529 not 'use_ozone=true' in vals['gn_args']) | 479 not 'use_ozone=true' in vals['gn_args']) |
530 | 480 |
531 asan = 'is_asan=true' in vals['gn_args'] | 481 asan = 'is_asan=true' in vals['gn_args'] |
532 msan = 'is_msan=true' in vals['gn_args'] | 482 msan = 'is_msan=true' in vals['gn_args'] |
(...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
676 | 626 |
677 ret = 0 | 627 ret = 0 |
678 response_file = self.TempFile() | 628 response_file = self.TempFile() |
679 response_file.write('\n'.join(inp['files']) + '\n') | 629 response_file.write('\n'.join(inp['files']) + '\n') |
680 response_file.close() | 630 response_file.close() |
681 | 631 |
682 matching_targets = [] | 632 matching_targets = [] |
683 try: | 633 try: |
684 cmd = self.GNCmd('refs', self.args.path[0]) + [ | 634 cmd = self.GNCmd('refs', self.args.path[0]) + [ |
685 '@%s' % response_file.name, '--all', '--as=output'] | 635 '@%s' % response_file.name, '--all', '--as=output'] |
686 ret, out, _ = self.Run(cmd) | 636 ret, out, _ = self.Run(cmd, force_verbose=False) |
687 if ret and not 'The input matches no targets' in out: | 637 if ret and not 'The input matches no targets' in out: |
688 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), | 638 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), |
689 output_path) | 639 output_path) |
690 build_dir = self.ToSrcRelPath(self.args.path[0]) + os.sep | 640 build_dir = self.ToSrcRelPath(self.args.path[0]) + os.sep |
691 for output in out.splitlines(): | 641 for output in out.splitlines(): |
692 build_output = output.replace(build_dir, '') | 642 build_output = output.replace(build_dir, '') |
693 if build_output in inp['targets']: | 643 if build_output in inp['targets']: |
694 matching_targets.append(build_output) | 644 matching_targets.append(build_output) |
695 | 645 |
696 cmd = self.GNCmd('refs', self.args.path[0]) + [ | 646 cmd = self.GNCmd('refs', self.args.path[0]) + [ |
697 '@%s' % response_file.name, '--all'] | 647 '@%s' % response_file.name, '--all'] |
698 ret, out, _ = self.Run(cmd) | 648 ret, out, _ = self.Run(cmd, force_verbose=False) |
699 if ret and not 'The input matches no targets' in out: | 649 if ret and not 'The input matches no targets' in out: |
700 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), | 650 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), |
701 output_path) | 651 output_path) |
702 for label in out.splitlines(): | 652 for label in out.splitlines(): |
703 build_target = label[2:] | 653 build_target = label[2:] |
704 # We want to accept 'chrome/android:chrome_public_apk' and | 654 # We want to accept 'chrome/android:chrome_public_apk' and |
705 # just 'chrome_public_apk'. This may result in too many targets | 655 # just 'chrome_public_apk'. This may result in too many targets |
706 # getting built, but we can adjust that later if need be. | 656 # getting built, but we can adjust that later if need be. |
707 for input_target in inp['targets']: | 657 for input_target in inp['targets']: |
708 if (input_target == build_target or | 658 if (input_target == build_target or |
709 build_target.endswith(':' + input_target)): | 659 build_target.endswith(':' + input_target)): |
710 matching_targets.append(input_target) | 660 matching_targets.append(input_target) |
711 finally: | 661 finally: |
712 self.RemoveFile(response_file.name) | 662 self.RemoveFile(response_file.name) |
713 | 663 |
714 if matching_targets: | 664 if matching_targets: |
715 # TODO: it could be that a target X might depend on a target Y | 665 # 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 | 666 # 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 | 667 # 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. | 668 # targets). I'm not sure if that optimization is generally worth it. |
719 self.WriteJSON({'targets': sorted(matching_targets), | 669 self.WriteJSON({'targets': sorted(set(matching_targets)), |
720 'build_targets': sorted(matching_targets), | 670 'build_targets': sorted(set(matching_targets)), |
721 'status': 'Found dependency'}, output_path) | 671 'status': 'Found dependency'}, output_path) |
722 else: | 672 else: |
723 self.WriteJSON({'targets': [], | 673 self.WriteJSON({'targets': [], |
724 'build_targets': [], | 674 'build_targets': [], |
725 'status': 'No dependency'}, output_path) | 675 'status': 'No dependency'}, output_path) |
726 | 676 |
727 if not ret and self.args.verbose: | 677 if self.args.verbose: |
728 outp = json.loads(self.ReadFile(output_path)) | 678 outp = json.loads(self.ReadFile(output_path)) |
729 self.Print() | 679 self.Print() |
730 self.Print('analyze output:') | 680 self.Print('analyze output:') |
731 self.PrintJSON(outp) | 681 self.PrintJSON(outp) |
732 self.Print() | 682 self.Print() |
733 | 683 |
734 return 0 | 684 return 0 |
735 | 685 |
736 def ReadInputJSON(self, required_keys): | 686 def ReadInputJSON(self, required_keys): |
737 path = self.args.input_path[0] | 687 path = self.args.input_path[0] |
738 output_path = self.args.output_path[0] | 688 output_path = self.args.output_path[0] |
739 if not self.Exists(path): | 689 if not self.Exists(path): |
740 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path) | 690 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path) |
741 | 691 |
742 try: | 692 try: |
743 inp = json.loads(self.ReadFile(path)) | 693 inp = json.loads(self.ReadFile(path)) |
744 except Exception as e: | 694 except Exception as e: |
745 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' % | 695 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' % |
746 (path, e), output_path) | 696 (path, e), output_path) |
747 | 697 |
748 for k in required_keys: | 698 for k in required_keys: |
749 if not k in inp: | 699 if not k in inp: |
750 self.WriteFailureAndRaise('input file is missing a "%s" key' % k, | 700 self.WriteFailureAndRaise('input file is missing a "%s" key' % k, |
751 output_path) | 701 output_path) |
752 | 702 |
753 return inp | 703 return inp |
754 | 704 |
755 def WriteFailureAndRaise(self, msg, output_path): | 705 def WriteFailureAndRaise(self, msg, output_path): |
756 if output_path: | 706 if output_path: |
757 self.WriteJSON({'error': msg}, output_path) | 707 self.WriteJSON({'error': msg}, output_path, force_verbose=True) |
758 raise MBErr(msg) | 708 raise MBErr(msg) |
759 | 709 |
760 def WriteJSON(self, obj, path): | 710 def WriteJSON(self, obj, path, force_verbose=False): |
761 try: | 711 try: |
762 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n') | 712 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n', |
| 713 force_verbose=force_verbose) |
763 except Exception as e: | 714 except Exception as e: |
764 raise MBErr('Error %s writing to the output path "%s"' % | 715 raise MBErr('Error %s writing to the output path "%s"' % |
765 (e, path)) | 716 (e, path)) |
766 | 717 |
767 def PrintCmd(self, cmd): | 718 def PrintCmd(self, cmd): |
768 if cmd[0] == sys.executable: | 719 if cmd[0] == sys.executable: |
769 cmd = ['python'] + cmd[1:] | 720 cmd = ['python'] + cmd[1:] |
770 self.Print(*[pipes.quote(c) for c in cmd]) | 721 self.Print(*[pipes.quote(c) for c in cmd]) |
771 | 722 |
772 def PrintJSON(self, obj): | 723 def PrintJSON(self, obj): |
773 self.Print(json.dumps(obj, indent=2, sort_keys=True)) | 724 self.Print(json.dumps(obj, indent=2, sort_keys=True)) |
774 | 725 |
775 def Print(self, *args, **kwargs): | 726 def Print(self, *args, **kwargs): |
776 # This function largely exists so it can be overridden for testing. | 727 # This function largely exists so it can be overridden for testing. |
777 print(*args, **kwargs) | 728 print(*args, **kwargs) |
778 | 729 |
779 def Run(self, cmd, env=None): | 730 def Run(self, cmd, env=None, force_verbose=True): |
780 # This function largely exists so it can be overridden for testing. | 731 # This function largely exists so it can be overridden for testing. |
781 if self.args.dryrun or self.args.verbose: | 732 if self.args.dryrun or self.args.verbose or force_verbose: |
782 self.PrintCmd(cmd) | 733 self.PrintCmd(cmd) |
783 if self.args.dryrun: | 734 if self.args.dryrun: |
784 return 0, '', '' | 735 return 0, '', '' |
| 736 |
785 ret, out, err = self.Call(cmd, env=env) | 737 ret, out, err = self.Call(cmd, env=env) |
786 if self.args.verbose: | 738 if self.args.verbose or force_verbose: |
787 if out: | 739 if out: |
788 self.Print(out, end='') | 740 self.Print(out, end='') |
789 if err: | 741 if err: |
790 self.Print(err, end='', file=sys.stderr) | 742 self.Print(err, end='', file=sys.stderr) |
791 return ret, out, err | 743 return ret, out, err |
792 | 744 |
793 def Call(self, cmd, env=None): | 745 def Call(self, cmd, env=None): |
794 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir, | 746 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir, |
795 stdout=subprocess.PIPE, stderr=subprocess.PIPE, | 747 stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
796 env=env) | 748 env=env) |
(...skipping 21 matching lines...) Expand all Loading... |
818 return fp.read() | 770 return fp.read() |
819 | 771 |
820 def RemoveFile(self, path): | 772 def RemoveFile(self, path): |
821 # This function largely exists so it can be overriden for testing. | 773 # This function largely exists so it can be overriden for testing. |
822 os.remove(path) | 774 os.remove(path) |
823 | 775 |
824 def TempFile(self, mode='w'): | 776 def TempFile(self, mode='w'): |
825 # This function largely exists so it can be overriden for testing. | 777 # This function largely exists so it can be overriden for testing. |
826 return tempfile.NamedTemporaryFile(mode=mode, delete=False) | 778 return tempfile.NamedTemporaryFile(mode=mode, delete=False) |
827 | 779 |
828 def WriteFile(self, path, contents): | 780 def WriteFile(self, path, contents, force_verbose=False): |
829 # This function largely exists so it can be overriden for testing. | 781 # This function largely exists so it can be overriden for testing. |
830 if self.args.dryrun or self.args.verbose: | 782 if self.args.dryrun or self.args.verbose or force_verbose: |
831 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path)) | 783 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path)) |
832 with open(path, 'w') as fp: | 784 with open(path, 'w') as fp: |
833 return fp.write(contents) | 785 return fp.write(contents) |
834 | 786 |
835 | 787 |
836 class MBErr(Exception): | 788 class MBErr(Exception): |
837 pass | 789 pass |
838 | 790 |
839 | 791 |
840 if __name__ == '__main__': | 792 if __name__ == '__main__': |
841 try: | 793 try: |
842 sys.exit(main(sys.argv[1:])) | 794 sys.exit(main(sys.argv[1:])) |
843 except MBErr as e: | 795 except MBErr as e: |
844 print(e) | 796 print(e) |
845 sys.exit(1) | 797 sys.exit(1) |
846 except KeyboardInterrupt: | 798 except KeyboardInterrupt: |
847 print("interrupted, exiting", stream=sys.stderr) | 799 print("interrupted, exiting", stream=sys.stderr) |
848 sys.exit(130) | 800 sys.exit(130) |
OLD | NEW |