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 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
71 return 1 | 71 return 1 |
72 | 72 |
73 def ParseArgs(self, argv): | 73 def ParseArgs(self, argv): |
74 def AddCommonOptions(subp): | 74 def AddCommonOptions(subp): |
75 subp.add_argument('-b', '--builder', | 75 subp.add_argument('-b', '--builder', |
76 help='builder name to look up config from') | 76 help='builder name to look up config from') |
77 subp.add_argument('-m', '--master', | 77 subp.add_argument('-m', '--master', |
78 help='master name to look up config from') | 78 help='master name to look up config from') |
79 subp.add_argument('-c', '--config', | 79 subp.add_argument('-c', '--config', |
80 help='configuration to analyze') | 80 help='configuration to analyze') |
| 81 subp.add_argument('--phase', type=int, |
| 82 help=('build phase for a given build ' |
| 83 '(int in [1, 2, ...))')) |
81 subp.add_argument('-f', '--config-file', metavar='PATH', | 84 subp.add_argument('-f', '--config-file', metavar='PATH', |
82 default=self.default_config, | 85 default=self.default_config, |
83 help='path to config file ' | 86 help='path to config file ' |
84 '(default is //tools/mb/mb_config.pyl)') | 87 '(default is //tools/mb/mb_config.pyl)') |
85 subp.add_argument('-g', '--goma-dir', | 88 subp.add_argument('-g', '--goma-dir', |
86 help='path to goma directory') | 89 help='path to goma directory') |
87 subp.add_argument('--gyp-script', metavar='PATH', | 90 subp.add_argument('--gyp-script', metavar='PATH', |
88 default=self.PathJoin('build', 'gyp_chromium'), | 91 default=self.PathJoin('build', 'gyp_chromium'), |
89 help='path to gyp script relative to project root ' | 92 help='path to gyp script relative to project root ' |
90 '(default is %(default)s)') | 93 '(default is %(default)s)') |
(...skipping 230 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
321 def CmdValidate(self, print_ok=True): | 324 def CmdValidate(self, print_ok=True): |
322 errs = [] | 325 errs = [] |
323 | 326 |
324 # Read the file to make sure it parses. | 327 # Read the file to make sure it parses. |
325 self.ReadConfigFile() | 328 self.ReadConfigFile() |
326 | 329 |
327 # Build a list of all of the configs referenced by builders. | 330 # Build a list of all of the configs referenced by builders. |
328 all_configs = {} | 331 all_configs = {} |
329 for master in self.masters: | 332 for master in self.masters: |
330 for config in self.masters[master].values(): | 333 for config in self.masters[master].values(): |
331 all_configs[config] = master | 334 if isinstance(config, list): |
| 335 for c in config: |
| 336 all_configs[c] = master |
| 337 else: |
| 338 all_configs[config] = master |
332 | 339 |
333 # Check that every referenced args file or config actually exists. | 340 # Check that every referenced args file or config actually exists. |
334 for config, loc in all_configs.items(): | 341 for config, loc in all_configs.items(): |
335 if config.startswith('//'): | 342 if config.startswith('//'): |
336 if not self.Exists(self.ToAbsPath(config)): | 343 if not self.Exists(self.ToAbsPath(config)): |
337 errs.append('Unknown args file "%s" referenced from "%s".' % | 344 errs.append('Unknown args file "%s" referenced from "%s".' % |
338 (config, loc)) | 345 (config, loc)) |
339 elif not config in self.configs: | 346 elif not config in self.configs: |
340 errs.append('Unknown config "%s" referenced from "%s".' % | 347 errs.append('Unknown config "%s" referenced from "%s".' % |
341 (config, loc)) | 348 (config, loc)) |
(...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
468 config_only = config_builders - master_builders | 475 config_only = config_builders - master_builders |
469 tbd = set() | 476 tbd = set() |
470 gyp = set() | 477 gyp = set() |
471 done = set() | 478 done = set() |
472 notes = {builder: '' for builder in config_builders | master_builders} | 479 notes = {builder: '' for builder in config_builders | master_builders} |
473 | 480 |
474 for builder in both: | 481 for builder in both: |
475 config = self.masters[master][builder] | 482 config = self.masters[master][builder] |
476 if config == 'tbd': | 483 if config == 'tbd': |
477 tbd.add(builder) | 484 tbd.add(builder) |
| 485 elif isinstance(config, list): |
| 486 vals = self.FlattenConfig(config[0]) |
| 487 if vals['type'] == 'gyp': |
| 488 gyp.add(builder) |
| 489 else: |
| 490 done.add(builder) |
478 elif config.startswith('//'): | 491 elif config.startswith('//'): |
479 done.add(builder) | 492 done.add(builder) |
480 else: | 493 else: |
481 # TODO(dpranke): Check if MB is actually running? | |
482 vals = self.FlattenConfig(config) | 494 vals = self.FlattenConfig(config) |
483 if vals['type'] == 'gyp': | 495 if vals['type'] == 'gyp': |
484 gyp.add(builder) | 496 gyp.add(builder) |
485 else: | 497 else: |
486 done.add(builder) | 498 done.add(builder) |
487 | 499 |
488 if self.args.check_compile and (tbd or master_only): | 500 if self.args.check_compile and (tbd or master_only): |
489 either = tbd | master_only | 501 either = tbd | master_only |
490 for builder in either: | 502 for builder in either: |
491 notes[builder] = ' (' + self.CheckCompile(master, builder) +')' | 503 notes[builder] = ' (' + self.CheckCompile(master, builder) +')' |
(...skipping 23 matching lines...) Expand all Loading... |
515 | 527 |
516 vals = {} | 528 vals = {} |
517 if self.args.builder or self.args.master or self.args.config: | 529 if self.args.builder or self.args.master or self.args.config: |
518 vals = self.Lookup() | 530 vals = self.Lookup() |
519 if vals['type'] == 'gn': | 531 if vals['type'] == 'gn': |
520 # Re-run gn gen in order to ensure the config is consistent with the | 532 # Re-run gn gen in order to ensure the config is consistent with the |
521 # build dir. | 533 # build dir. |
522 self.RunGNGen(vals) | 534 self.RunGNGen(vals) |
523 return vals | 535 return vals |
524 | 536 |
525 # TODO: We can only get the config for GN build dirs, not GYP build dirs. | |
526 # GN stores the args that were used in args.gn in the build dir, | |
527 # but GYP doesn't store them anywhere. We should consider modifying | |
528 # gyp_chromium to record the arguments it runs with in a similar | |
529 # manner. | |
530 | |
531 mb_type_path = self.PathJoin(self.ToAbsPath(build_dir), 'mb_type') | 537 mb_type_path = self.PathJoin(self.ToAbsPath(build_dir), 'mb_type') |
532 if not self.Exists(mb_type_path): | 538 if not self.Exists(mb_type_path): |
533 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir), | 539 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir), |
534 'toolchain.ninja') | 540 'toolchain.ninja') |
535 if not self.Exists(toolchain_path): | 541 if not self.Exists(toolchain_path): |
536 self.Print('Must either specify a path to an existing GN build dir ' | 542 self.Print('Must either specify a path to an existing GN build dir ' |
537 'or pass in a -m/-b pair or a -c flag to specify the ' | 543 'or pass in a -m/-b pair or a -c flag to specify the ' |
538 'configuration') | 544 'configuration') |
539 return {} | 545 return {} |
540 else: | 546 else: |
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
649 '(-m/--master and -b/--builder)') | 655 '(-m/--master and -b/--builder)') |
650 | 656 |
651 if not self.args.master in self.masters: | 657 if not self.args.master in self.masters: |
652 raise MBErr('Master name "%s" not found in "%s"' % | 658 raise MBErr('Master name "%s" not found in "%s"' % |
653 (self.args.master, self.args.config_file)) | 659 (self.args.master, self.args.config_file)) |
654 | 660 |
655 if not self.args.builder in self.masters[self.args.master]: | 661 if not self.args.builder in self.masters[self.args.master]: |
656 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' % | 662 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' % |
657 (self.args.builder, self.args.master, self.args.config_file)) | 663 (self.args.builder, self.args.master, self.args.config_file)) |
658 | 664 |
659 return self.masters[self.args.master][self.args.builder] | 665 config = self.masters[self.args.master][self.args.builder] |
| 666 if isinstance(config, list): |
| 667 if self.args.phase is None: |
| 668 raise MBErr('Must specify a build --phase for %s on %s' % |
| 669 (self.args.builder, self.args.master)) |
| 670 phase = int(self.args.phase) |
| 671 if phase < 1 or phase > len(config): |
| 672 raise MBErr('Phase %d out of bounds for %s on %s' % |
| 673 (phase, self.args.builder, self.args.master)) |
| 674 return config[phase-1] |
| 675 |
| 676 if self.args.phase is not None: |
| 677 raise MBErr('Must not specify a build --phase for %s on %s' % |
| 678 (self.args.builder, self.args.master)) |
| 679 return config |
660 | 680 |
661 def FlattenConfig(self, config): | 681 def FlattenConfig(self, config): |
662 mixins = self.configs[config] | 682 mixins = self.configs[config] |
663 # TODO(dpranke): We really should provide a constructor for the | |
664 # default set of values. | |
665 vals = { | 683 vals = { |
666 'args_file': '', | 684 'args_file': '', |
667 'cros_passthrough': False, | 685 'cros_passthrough': False, |
668 'gn_args': [], | 686 'gn_args': [], |
669 'gyp_defines': '', | 687 'gyp_defines': '', |
670 'gyp_crosscompile': False, | 688 'gyp_crosscompile': False, |
671 'type': None, | 689 'type': None, |
672 } | 690 } |
673 | 691 |
674 visited = [] | 692 visited = [] |
675 self.FlattenMixins(mixins, vals, visited) | 693 self.FlattenMixins(mixins, vals, visited) |
676 return vals | 694 return vals |
677 | 695 |
678 def FlattenMixins(self, mixins, vals, visited): | 696 def FlattenMixins(self, mixins, vals, visited): |
679 for m in mixins: | 697 for m in mixins: |
680 if m not in self.mixins: | 698 if m not in self.mixins: |
681 raise MBErr('Unknown mixin "%s"' % m) | 699 raise MBErr('Unknown mixin "%s"' % m) |
682 | 700 |
683 # TODO: check for cycles in mixins. | |
684 | |
685 visited.append(m) | 701 visited.append(m) |
686 | 702 |
687 mixin_vals = self.mixins[m] | 703 mixin_vals = self.mixins[m] |
688 | 704 |
689 if 'cros_passthrough' in mixin_vals: | 705 if 'cros_passthrough' in mixin_vals: |
690 vals['cros_passthrough'] = mixin_vals['cros_passthrough'] | 706 vals['cros_passthrough'] = mixin_vals['cros_passthrough'] |
691 if 'gn_args' in mixin_vals: | 707 if 'gn_args' in mixin_vals: |
692 if vals['gn_args']: | 708 if vals['gn_args']: |
693 vals['gn_args'] += ' ' + mixin_vals['gn_args'] | 709 vals['gn_args'] += ' ' + mixin_vals['gn_args'] |
694 else: | 710 else: |
(...skipping 274 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
969 self.PrintJSON(outp) | 985 self.PrintJSON(outp) |
970 self.Print() | 986 self.Print() |
971 | 987 |
972 return ret | 988 return ret |
973 | 989 |
974 def GetIsolateCommand(self, target, vals, gn_isolate_map): | 990 def GetIsolateCommand(self, target, vals, gn_isolate_map): |
975 android = 'target_os="android"' in vals['gn_args'] | 991 android = 'target_os="android"' in vals['gn_args'] |
976 | 992 |
977 # This needs to mirror the settings in //build/config/ui.gni: | 993 # This needs to mirror the settings in //build/config/ui.gni: |
978 # use_x11 = is_linux && !use_ozone. | 994 # use_x11 = is_linux && !use_ozone. |
979 # TODO(dpranke): Figure out how to keep this in sync better. | |
980 use_x11 = (self.platform == 'linux2' and | 995 use_x11 = (self.platform == 'linux2' and |
981 not android and | 996 not android and |
982 not 'use_ozone=true' in vals['gn_args']) | 997 not 'use_ozone=true' in vals['gn_args']) |
983 | 998 |
984 asan = 'is_asan=true' in vals['gn_args'] | 999 asan = 'is_asan=true' in vals['gn_args'] |
985 msan = 'is_msan=true' in vals['gn_args'] | 1000 msan = 'is_msan=true' in vals['gn_args'] |
986 tsan = 'is_tsan=true' in vals['gn_args'] | 1001 tsan = 'is_tsan=true' in vals['gn_args'] |
987 | 1002 |
988 target_name = self.GNTargetName(target) | 1003 target_name = self.GNTargetName(target) |
989 test_type = gn_isolate_map[target_name]['type'] | 1004 test_type = gn_isolate_map[target_name]['type'] |
(...skipping 496 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1486 # Then check to see if the arg contains any metacharacters other than | 1501 # Then check to see if the arg contains any metacharacters other than |
1487 # double quotes; if it does, quote everything (including the double | 1502 # double quotes; if it does, quote everything (including the double |
1488 # quotes) for safety. | 1503 # quotes) for safety. |
1489 if any(a in UNSAFE_FOR_CMD for a in arg): | 1504 if any(a in UNSAFE_FOR_CMD for a in arg): |
1490 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg) | 1505 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg) |
1491 return arg | 1506 return arg |
1492 | 1507 |
1493 | 1508 |
1494 if __name__ == '__main__': | 1509 if __name__ == '__main__': |
1495 sys.exit(main(sys.argv[1:])) | 1510 sys.exit(main(sys.argv[1:])) |
OLD | NEW |