| 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 | 26 import urllib2 |
| 27 | 27 |
| 28 from collections import OrderedDict | 28 from collections import OrderedDict |
| 29 | 29 |
| 30 CHROMIUM_SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname( |
| 31 os.path.abspath(__file__)))) |
| 32 sys.path = [os.path.join(CHROMIUM_SRC_DIR, 'build')] + sys.path |
| 33 |
| 34 import gn_helpers |
| 35 |
| 36 |
| 30 def main(args): | 37 def main(args): |
| 31 mbw = MetaBuildWrapper() | 38 mbw = MetaBuildWrapper() |
| 32 return mbw.Main(args) | 39 return mbw.Main(args) |
| 33 | 40 |
| 34 | 41 |
| 35 class MetaBuildWrapper(object): | 42 class MetaBuildWrapper(object): |
| 36 def __init__(self): | 43 def __init__(self): |
| 37 p = os.path | 44 self.chromium_src_dir = CHROMIUM_SRC_DIR |
| 38 d = os.path.dirname | 45 self.default_config = os.path.join(self.chromium_src_dir, 'tools', 'mb', |
| 39 self.chromium_src_dir = p.normpath(d(d(d(p.abspath(__file__))))) | 46 'mb_config.pyl') |
| 40 self.default_config = p.join(self.chromium_src_dir, 'tools', 'mb', | |
| 41 'mb_config.pyl') | |
| 42 self.executable = sys.executable | 47 self.executable = sys.executable |
| 43 self.platform = sys.platform | 48 self.platform = sys.platform |
| 44 self.sep = os.sep | 49 self.sep = os.sep |
| 45 self.args = argparse.Namespace() | 50 self.args = argparse.Namespace() |
| 46 self.configs = {} | 51 self.configs = {} |
| 47 self.masters = {} | 52 self.masters = {} |
| 48 self.mixins = {} | 53 self.mixins = {} |
| 49 | 54 |
| 50 def Main(self, args): | 55 def Main(self, args): |
| 51 self.ParseArgs(args) | 56 self.ParseArgs(args) |
| (...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 248 return 1 | 253 return 1 |
| 249 | 254 |
| 250 if vals['type'] == 'gn': | 255 if vals['type'] == 'gn': |
| 251 return self.RunGNIsolate(vals) | 256 return self.RunGNIsolate(vals) |
| 252 else: | 257 else: |
| 253 return self.Build('%s_run' % self.args.target[0]) | 258 return self.Build('%s_run' % self.args.target[0]) |
| 254 | 259 |
| 255 def CmdLookup(self): | 260 def CmdLookup(self): |
| 256 vals = self.Lookup() | 261 vals = self.Lookup() |
| 257 if vals['type'] == 'gn': | 262 if vals['type'] == 'gn': |
| 258 cmd = self.GNCmd('gen', '_path_', vals['gn_args']) | 263 cmd = self.GNCmd('gen', '_path_') |
| 264 gn_args = self.GNArgs(vals) |
| 265 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args) |
| 259 env = None | 266 env = None |
| 260 else: | 267 else: |
| 261 cmd, env = self.GYPCmd('_path_', vals) | 268 cmd, env = self.GYPCmd('_path_', vals) |
| 262 | 269 |
| 263 self.PrintCmd(cmd, env) | 270 self.PrintCmd(cmd, env) |
| 264 return 0 | 271 return 0 |
| 265 | 272 |
| 266 def CmdRun(self): | 273 def CmdRun(self): |
| 267 vals = self.GetConfig() | 274 vals = self.GetConfig() |
| 268 if not vals: | 275 if not vals: |
| (...skipping 414 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 683 | 690 |
| 684 if needs_clobber: | 691 if needs_clobber: |
| 685 self.RemoveDirectory(build_dir) | 692 self.RemoveDirectory(build_dir) |
| 686 | 693 |
| 687 self.MaybeMakeDirectory(build_dir) | 694 self.MaybeMakeDirectory(build_dir) |
| 688 self.WriteFile(mb_type_path, new_mb_type) | 695 self.WriteFile(mb_type_path, new_mb_type) |
| 689 | 696 |
| 690 def RunGNGen(self, vals): | 697 def RunGNGen(self, vals): |
| 691 build_dir = self.args.path[0] | 698 build_dir = self.args.path[0] |
| 692 | 699 |
| 693 cmd = self.GNCmd('gen', build_dir, vals['gn_args'], extra_args=['--check']) | 700 cmd = self.GNCmd('gen', build_dir, '--check') |
| 701 gn_args = self.GNArgs(vals) |
| 702 |
| 703 # Since GN hasn't run yet, the build directory may not even exist. |
| 704 self.MaybeMakeDirectory(self.ToAbsPath(build_dir)) |
| 705 |
| 706 gn_args_path = self.ToAbsPath(build_dir, 'args.gn') |
| 707 self.WriteFile(gn_args_path, gn_args) |
| 694 | 708 |
| 695 swarming_targets = [] | 709 swarming_targets = [] |
| 696 if getattr(self.args, 'swarming_targets_file', None): | 710 if getattr(self.args, 'swarming_targets_file', None): |
| 697 # We need GN to generate the list of runtime dependencies for | 711 # We need GN to generate the list of runtime dependencies for |
| 698 # the compile targets listed (one per line) in the file so | 712 # the compile targets listed (one per line) in the file so |
| 699 # we can run them via swarming. We use ninja_to_gn.pyl to convert | 713 # we can run them via swarming. We use ninja_to_gn.pyl to convert |
| 700 # the compile targets to the matching GN labels. | 714 # the compile targets to the matching GN labels. |
| 701 path = self.args.swarming_targets_file | 715 path = self.args.swarming_targets_file |
| 702 if not self.Exists(path): | 716 if not self.Exists(path): |
| 703 self.WriteFailureAndRaise('"%s" does not exist' % path, | 717 self.WriteFailureAndRaise('"%s" does not exist' % path, |
| (...skipping 11 matching lines...) Expand all Loading... |
| 715 elif gn_isolate_map[target_name]['type'] == 'unknown': | 729 elif gn_isolate_map[target_name]['type'] == 'unknown': |
| 716 err += ('test target "%s" type is unknown\n' % target_name) | 730 err += ('test target "%s" type is unknown\n' % target_name) |
| 717 else: | 731 else: |
| 718 gn_labels.append(gn_isolate_map[target_name]['label']) | 732 gn_labels.append(gn_isolate_map[target_name]['label']) |
| 719 | 733 |
| 720 if err: | 734 if err: |
| 721 raise MBErr('Error: Failed to match swarming targets to %s:\n%s' % | 735 raise MBErr('Error: Failed to match swarming targets to %s:\n%s' % |
| 722 ('//testing/buildbot/gn_isolate_map.pyl', err)) | 736 ('//testing/buildbot/gn_isolate_map.pyl', err)) |
| 723 | 737 |
| 724 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps') | 738 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps') |
| 725 | |
| 726 # Since GN hasn't run yet, the build directory may not even exist. | |
| 727 self.MaybeMakeDirectory(self.ToAbsPath(build_dir)) | |
| 728 | |
| 729 self.WriteFile(gn_runtime_deps_path, '\n'.join(gn_labels) + '\n') | 739 self.WriteFile(gn_runtime_deps_path, '\n'.join(gn_labels) + '\n') |
| 730 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path) | 740 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path) |
| 731 | 741 |
| 732 ret, _, _ = self.Run(cmd) | 742 ret, _, _ = self.Run(cmd) |
| 733 if ret: | 743 if ret: |
| 734 # If `gn gen` failed, we should exit early rather than trying to | 744 # If `gn gen` failed, we should exit early rather than trying to |
| 735 # generate isolates. Run() will have already logged any error output. | 745 # generate isolates. Run() will have already logged any error output. |
| 736 self.Print('GN gen failed: %d' % ret) | 746 self.Print('GN gen failed: %d' % ret) |
| 737 return ret | 747 return ret |
| 738 | 748 |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 785 def RunGNIsolate(self, vals): | 795 def RunGNIsolate(self, vals): |
| 786 gn_isolate_map = ast.literal_eval(self.ReadFile(self.PathJoin( | 796 gn_isolate_map = ast.literal_eval(self.ReadFile(self.PathJoin( |
| 787 self.chromium_src_dir, 'testing', 'buildbot', 'gn_isolate_map.pyl'))) | 797 self.chromium_src_dir, 'testing', 'buildbot', 'gn_isolate_map.pyl'))) |
| 788 | 798 |
| 789 build_dir = self.args.path[0] | 799 build_dir = self.args.path[0] |
| 790 target = self.args.target[0] | 800 target = self.args.target[0] |
| 791 target_name = self.GNTargetName(target) | 801 target_name = self.GNTargetName(target) |
| 792 command, extra_files = self.GetIsolateCommand(target, vals, gn_isolate_map) | 802 command, extra_files = self.GetIsolateCommand(target, vals, gn_isolate_map) |
| 793 | 803 |
| 794 label = gn_isolate_map[target_name]['label'] | 804 label = gn_isolate_map[target_name]['label'] |
| 795 cmd = self.GNCmd('desc', build_dir, extra_args=[label, 'runtime_deps']) | 805 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps') |
| 796 ret, out, _ = self.Call(cmd) | 806 ret, out, _ = self.Call(cmd) |
| 797 if ret: | 807 if ret: |
| 798 if out: | 808 if out: |
| 799 self.Print(out) | 809 self.Print(out) |
| 800 return ret | 810 return ret |
| 801 | 811 |
| 802 runtime_deps = out.splitlines() | 812 runtime_deps = out.splitlines() |
| 803 | 813 |
| 804 self.WriteIsolateFiles(build_dir, command, target, runtime_deps, | 814 self.WriteIsolateFiles(build_dir, command, target, runtime_deps, |
| 805 extra_files) | 815 extra_files) |
| (...skipping 28 matching lines...) Expand all Loading... |
| 834 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)), | 844 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)), |
| 835 '--isolate', | 845 '--isolate', |
| 836 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)), | 846 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)), |
| 837 ], | 847 ], |
| 838 'dir': self.chromium_src_dir, | 848 'dir': self.chromium_src_dir, |
| 839 'version': 1, | 849 'version': 1, |
| 840 }, | 850 }, |
| 841 isolate_path + 'd.gen.json', | 851 isolate_path + 'd.gen.json', |
| 842 ) | 852 ) |
| 843 | 853 |
| 844 def GNCmd(self, subcommand, path, gn_args='', extra_args=None): | 854 def GNCmd(self, subcommand, path, *args): |
| 845 if self.platform == 'linux2': | 855 if self.platform == 'linux2': |
| 846 subdir, exe = 'linux64', 'gn' | 856 subdir, exe = 'linux64', 'gn' |
| 847 elif self.platform == 'darwin': | 857 elif self.platform == 'darwin': |
| 848 subdir, exe = 'mac', 'gn' | 858 subdir, exe = 'mac', 'gn' |
| 849 else: | 859 else: |
| 850 subdir, exe = 'win', 'gn.exe' | 860 subdir, exe = 'win', 'gn.exe' |
| 861 |
| 851 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe) | 862 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe) |
| 852 | 863 |
| 853 cmd = [gn_path, subcommand, path] | 864 return [gn_path, subcommand, path] + list(args) |
| 865 |
| 866 def GNArgs(self, vals): |
| 867 gn_args = vals['gn_args'] |
| 854 if self.args.goma_dir: | 868 if self.args.goma_dir: |
| 855 gn_args += ' goma_dir="%s"' % self.args.goma_dir | 869 gn_args += ' goma_dir="%s"' % self.args.goma_dir |
| 856 if gn_args: | 870 |
| 857 cmd.append('--args=%s' % gn_args) | 871 # Canonicalize the arg string into a sorted, newline-separated list |
| 858 if extra_args: | 872 # of key-value pairs, and de-dup the keys if need be so that only |
| 859 cmd.extend(extra_args) | 873 # the last instance of each arg is listed. |
| 860 return cmd | 874 gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args)) |
| 875 |
| 876 return gn_args |
| 861 | 877 |
| 862 def RunGYPGen(self, vals): | 878 def RunGYPGen(self, vals): |
| 863 path = self.args.path[0] | 879 path = self.args.path[0] |
| 864 | 880 |
| 865 output_dir = self.ParseGYPConfigPath(path) | 881 output_dir = self.ParseGYPConfigPath(path) |
| 866 cmd, env = self.GYPCmd(output_dir, vals) | 882 cmd, env = self.GYPCmd(output_dir, vals) |
| 867 ret, _, _ = self.Run(cmd, env=env) | 883 ret, _, _ = self.Run(cmd, env=env) |
| 868 return ret | 884 return ret |
| 869 | 885 |
| 870 def RunGYPAnalyze(self, vals): | 886 def RunGYPAnalyze(self, vals): |
| (...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1079 }, output_path) | 1095 }, output_path) |
| 1080 return 0 | 1096 return 0 |
| 1081 | 1097 |
| 1082 ret = 0 | 1098 ret = 0 |
| 1083 response_file = self.TempFile() | 1099 response_file = self.TempFile() |
| 1084 response_file.write('\n'.join(inp['files']) + '\n') | 1100 response_file.write('\n'.join(inp['files']) + '\n') |
| 1085 response_file.close() | 1101 response_file.close() |
| 1086 | 1102 |
| 1087 matching_targets = set() | 1103 matching_targets = set() |
| 1088 try: | 1104 try: |
| 1089 cmd = self.GNCmd('refs', self.args.path[0]) + [ | 1105 cmd = self.GNCmd('refs', |
| 1090 '@%s' % response_file.name, '--all', '--as=output'] | 1106 self.args.path[0], |
| 1107 '@%s' % response_file.name, |
| 1108 '--all', |
| 1109 '--as=output') |
| 1091 ret, out, _ = self.Run(cmd, force_verbose=False) | 1110 ret, out, _ = self.Run(cmd, force_verbose=False) |
| 1092 if ret and not 'The input matches no targets' in out: | 1111 if ret and not 'The input matches no targets' in out: |
| 1093 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), | 1112 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), |
| 1094 output_path) | 1113 output_path) |
| 1095 build_dir = self.ToSrcRelPath(self.args.path[0]) + self.sep | 1114 build_dir = self.ToSrcRelPath(self.args.path[0]) + self.sep |
| 1096 for output in out.splitlines(): | 1115 for output in out.splitlines(): |
| 1097 build_output = output.replace(build_dir, '') | 1116 build_output = output.replace(build_dir, '') |
| 1098 if build_output in targets: | 1117 if build_output in targets: |
| 1099 matching_targets.add(build_output) | 1118 matching_targets.add(build_output) |
| 1100 | 1119 |
| 1101 cmd = self.GNCmd('refs', self.args.path[0]) + [ | 1120 cmd = self.GNCmd('refs', |
| 1102 '@%s' % response_file.name, '--all'] | 1121 self.args.path[0], |
| 1122 '@%s' % response_file.name, |
| 1123 '--all') |
| 1103 ret, out, _ = self.Run(cmd, force_verbose=False) | 1124 ret, out, _ = self.Run(cmd, force_verbose=False) |
| 1104 if ret and not 'The input matches no targets' in out: | 1125 if ret and not 'The input matches no targets' in out: |
| 1105 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), | 1126 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), |
| 1106 output_path) | 1127 output_path) |
| 1107 for label in out.splitlines(): | 1128 for label in out.splitlines(): |
| 1108 build_target = label[2:] | 1129 build_target = label[2:] |
| 1109 # We want to accept 'chrome/android:chrome_public_apk' and | 1130 # We want to accept 'chrome/android:chrome_public_apk' and |
| 1110 # just 'chrome_public_apk'. This may result in too many targets | 1131 # just 'chrome_public_apk'. This may result in too many targets |
| 1111 # getting built, but we can adjust that later if need be. | 1132 # getting built, but we can adjust that later if need be. |
| 1112 for input_target in targets: | 1133 for input_target in targets: |
| (...skipping 241 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1354 # Then check to see if the arg contains any metacharacters other than | 1375 # Then check to see if the arg contains any metacharacters other than |
| 1355 # double quotes; if it does, quote everything (including the double | 1376 # double quotes; if it does, quote everything (including the double |
| 1356 # quotes) for safety. | 1377 # quotes) for safety. |
| 1357 if any(a in UNSAFE_FOR_CMD for a in arg): | 1378 if any(a in UNSAFE_FOR_CMD for a in arg): |
| 1358 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg) | 1379 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg) |
| 1359 return arg | 1380 return arg |
| 1360 | 1381 |
| 1361 | 1382 |
| 1362 if __name__ == '__main__': | 1383 if __name__ == '__main__': |
| 1363 sys.exit(main(sys.argv[1:])) | 1384 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |