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 json | 16 import json |
17 import os | 17 import os |
18 import pipes | 18 import pipes |
19 import pprint | |
19 import shlex | 20 import shlex |
20 import shutil | 21 import shutil |
21 import sys | 22 import sys |
22 import subprocess | 23 import subprocess |
23 import tempfile | 24 import tempfile |
24 | 25 |
25 | 26 |
26 def main(args): | 27 def main(args): |
27 mbw = MetaBuildWrapper() | 28 mbw = MetaBuildWrapper() |
28 mbw.ParseArgs(args) | 29 mbw.ParseArgs(args) |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
85 'as a JSON object.') | 86 'as a JSON object.') |
86 subp.set_defaults(func=self.CmdAnalyze) | 87 subp.set_defaults(func=self.CmdAnalyze) |
87 | 88 |
88 subp = subps.add_parser('gen', | 89 subp = subps.add_parser('gen', |
89 help='generate a new set of build files') | 90 help='generate a new set of build files') |
90 AddCommonOptions(subp) | 91 AddCommonOptions(subp) |
91 subp.add_argument('path', type=str, nargs=1, | 92 subp.add_argument('path', type=str, nargs=1, |
92 help='path to generate build into') | 93 help='path to generate build into') |
93 subp.set_defaults(func=self.CmdGen) | 94 subp.set_defaults(func=self.CmdGen) |
94 | 95 |
96 subp = subps.add_parser('isolate', | |
97 help='build isolates') | |
98 AddCommonOptions(subp) | |
99 subp.add_argument('path', type=str, nargs=1, | |
100 help='path build was generated into.') | |
101 subp.add_argument('input_path', nargs=1, | |
M-A Ruel
2015/06/04 15:52:21
why type=str at line 99 but not others? I'd recomm
Dirk Pranke
2015/06/04 20:30:32
no particular reason. Will remove.
| |
102 help='path to a file containing the input arguments ' | |
103 'as a JSON object.') | |
104 subp.add_argument('output_path', nargs=1, | |
105 help='path to a file containing the output arguments ' | |
106 'as a JSON object.') | |
107 subp.set_defaults(func=self.CmdIsolate) | |
108 | |
95 subp = subps.add_parser('lookup', | 109 subp = subps.add_parser('lookup', |
96 help='look up the command for a given config or ' | 110 help='look up the command for a given config or ' |
97 'builder') | 111 'builder') |
98 AddCommonOptions(subp) | 112 AddCommonOptions(subp) |
99 subp.set_defaults(func=self.CmdLookup) | 113 subp.set_defaults(func=self.CmdLookup) |
100 | 114 |
101 subp = subps.add_parser('validate', | 115 subp = subps.add_parser('validate', |
102 help='validate the config file') | 116 help='validate the config file') |
103 AddCommonOptions(subp) | 117 AddCommonOptions(subp) |
104 subp.set_defaults(func=self.CmdValidate) | 118 subp.set_defaults(func=self.CmdValidate) |
(...skipping 17 matching lines...) Expand all Loading... | |
122 | 136 |
123 def CmdGen(self): | 137 def CmdGen(self): |
124 vals = self.GetConfig() | 138 vals = self.GetConfig() |
125 if vals['type'] == 'gn': | 139 if vals['type'] == 'gn': |
126 return self.RunGNGen(self.args.path[0], vals) | 140 return self.RunGNGen(self.args.path[0], vals) |
127 if vals['type'] == 'gyp': | 141 if vals['type'] == 'gyp': |
128 return self.RunGYPGen(self.args.path[0], vals) | 142 return self.RunGYPGen(self.args.path[0], vals) |
129 | 143 |
130 raise MBErr('Unknown meta-build type "%s"' % vals['type']) | 144 raise MBErr('Unknown meta-build type "%s"' % vals['type']) |
131 | 145 |
146 def CmdIsolate(self): | |
147 vals = self.GetConfig() | |
148 if vals['type'] == 'gn': | |
149 return self.RunGNIsolate(vals) | |
150 if vals['type'] == 'gyp': | |
151 # For GYP builds the .isolate files are checked in and the | |
152 # .isolate.gen.json files are generated during the compile, | |
153 # so there is no work to do here. | |
154 return 0 | |
155 raise MBErr('Unknown meta-build type "%s"' % vals['type']) | |
156 | |
132 def CmdLookup(self): | 157 def CmdLookup(self): |
133 vals = self.GetConfig() | 158 vals = self.GetConfig() |
134 if vals['type'] == 'gn': | 159 if vals['type'] == 'gn': |
135 cmd = self.GNCmd('gen', '<path>', vals['gn_args']) | 160 cmd = self.GNCmd('gen', '<path>', vals['gn_args']) |
136 elif vals['type'] == 'gyp': | 161 elif vals['type'] == 'gyp': |
137 cmd = self.GYPCmd('<path>', vals['gyp_defines'], vals['gyp_config']) | 162 cmd = self.GYPCmd('<path>', vals['gyp_defines'], vals['gyp_config']) |
138 else: | 163 else: |
139 raise MBErr('Unknown meta-build type "%s"' % vals['type']) | 164 raise MBErr('Unknown meta-build type "%s"' % vals['type']) |
140 | 165 |
141 self.PrintCmd(cmd) | 166 self.PrintCmd(cmd) |
(...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
360 ret, _, _ = self.Run(cmd) | 385 ret, _, _ = self.Run(cmd) |
361 if not ret and self.args.verbose: | 386 if not ret and self.args.verbose: |
362 outp = json.loads(self.ReadFile(self.args.output_path[0])) | 387 outp = json.loads(self.ReadFile(self.args.output_path[0])) |
363 self.Print() | 388 self.Print() |
364 self.Print('analyze output:') | 389 self.Print('analyze output:') |
365 self.PrintJSON(inp) | 390 self.PrintJSON(inp) |
366 self.Print() | 391 self.Print() |
367 | 392 |
368 return ret | 393 return ret |
369 | 394 |
395 def RunGNIsolate(self, vals): | |
396 build_path = self.args.path[0] | |
397 inp = self.ReadInputJSON(['targets']) | |
398 if self.args.verbose: | |
399 self.Print() | |
400 self.Print('isolate input:') | |
401 self.PrintJSON(inp) | |
402 self.Print() | |
403 output_path = self.args.output_path[0] | |
404 | |
405 for target in inp['targets']: | |
406 runtime_deps_path = self.ToAbsPath(build_path, target + '.runtime_deps') | |
407 | |
408 if not self.Exists(runtime_deps_path): | |
409 self.WriteFailureAndRaise('"%s" does not exist' % runtime_deps_path, | |
410 output_path) | |
411 | |
412 command, extra_files = self.GetIsolateCommand(target, vals) | |
413 | |
414 runtime_deps = self.ReadFile(runtime_deps_path).splitlines() | |
415 | |
416 | |
417 isolate_path = self.ToAbsPath(build_path, target + '.isolate') | |
418 self.WriteFile(isolate_path, | |
419 pprint.pformat({ | |
420 'variables': { | |
421 'command': command, | |
422 'files': sorted(runtime_deps + extra_files), | |
423 'read_only': 1, | |
424 } | |
425 }) + '\n') | |
426 | |
427 self.WriteJSON( | |
428 { | |
429 'args': [ | |
430 '--isolated', | |
431 self.ToSrcRelPath('%s/%s.isolated' % (build_path, target)), | |
432 '--isolate', | |
433 self.ToSrcRelPath('%s/%s.isolate' % (build_path, target)), | |
434 ], | |
435 'dir': self.chromium_src_dir, | |
436 'version': 1, | |
437 }, | |
438 isolate_path + '.gen.json', | |
439 ) | |
440 | |
441 return 0 | |
442 | |
443 def GetIsolateCommand(self, target, vals): | |
444 output_path = self.args.output_path[0] | |
445 | |
446 extra_files = [] | |
447 | |
448 # TODO(dpranke): We should probably pull this from | |
449 # the test list info in //testing/buildbot/*.json, | |
450 # and assert that the test has can_use_on_swarming_builders: True, | |
451 # but we hardcode it here for now. | |
452 test_type = {}.get(target, 'gtest_test') | |
453 | |
454 # This needs to mirror the settings in //build/config/ui.gni: | |
455 # use_x11 = is_linux && !use_ozone. | |
456 # TODO(dpranke): Figure out how to keep this in sync better. | |
457 use_x11 = (sys.platform == 'linux2' and | |
458 not 'target_os="android"' in vals['gn_args'] and | |
459 not 'use_ozone=true' in vals['gn_args']) | |
460 | |
461 asan = 'is_asan=true' in vals['gn_args'] | |
462 msan = 'is_msan=true' in vals['gn_args'] | |
463 tsan = 'is_tsan=true' in vals['gn_args'] | |
464 | |
465 executable_suffix = '.exe' if sys.platform == 'win32' else '' | |
466 | |
467 if test_type == 'gtest_test': | |
468 extra_files.append('../../testing/test_env.py') | |
469 | |
470 if use_x11: | |
471 # TODO(dpranke): Figure out some way to figure out which | |
472 # test steps really need xvfb. | |
473 extra_files.append('xdisplaycheck') | |
474 extra_files.append('../../testing/xvfb.py') | |
475 | |
476 cmdline = [ | |
477 '../../testing/xvfb.py', | |
478 '.', | |
479 './' + str(target), | |
480 '--brave-new-test-launcher', | |
481 '--test-launcher-bot-mode', | |
482 '--asan=%d' % asan, | |
483 '--msan=%d' % msan, | |
484 '--tsan=%d' % tsan, | |
485 ] | |
486 else: | |
487 cmdline = [ | |
488 '../../testing/test_env.py', | |
489 '.', | |
490 './' + str(target) + executable_suffix, | |
491 '--brave-new-test-launcher', | |
492 '--test-launcher-bot-mode', | |
493 '--asan=%d' % asan, | |
494 '--msan=%d' % msan, | |
495 '--tsan=%d' % tsan, | |
496 ] | |
497 else: | |
498 # TODO(dpranke): Handle script_tests and other types of | |
499 # swarmed tests. | |
500 self.WriteFailureAndRaise('unknown test type "%s" for %s' % | |
501 (test_type, target), | |
502 output_path) | |
503 | |
504 | |
505 return cmdline, extra_files | |
506 | |
507 def ToAbsPath(self, build_path, relpath): | |
508 return os.path.join(self.chromium_src_dir, | |
509 self.ToSrcRelPath(build_path), | |
510 relpath) | |
511 | |
370 def ToSrcRelPath(self, path): | 512 def ToSrcRelPath(self, path): |
371 """Returns a relative path from the top of the repo.""" | 513 """Returns a relative path from the top of the repo.""" |
372 # TODO: Support normal paths in addition to source-absolute paths. | 514 # TODO: Support normal paths in addition to source-absolute paths. |
373 assert(path.startswith('//')) | 515 assert(path.startswith('//')) |
374 return path[2:] | 516 return path[2:] |
375 | 517 |
376 def ParseGYPConfigPath(self, path): | 518 def ParseGYPConfigPath(self, path): |
377 rpath = self.ToSrcRelPath(path) | 519 rpath = self.ToSrcRelPath(path) |
378 output_dir, _, config = rpath.rpartition('/') | 520 output_dir, _, config = rpath.rpartition('/') |
379 self.CheckGYPConfigIsSupported(config, path) | 521 self.CheckGYPConfigIsSupported(config, path) |
(...skipping 14 matching lines...) Expand all Loading... | |
394 '-G', | 536 '-G', |
395 'output_dir=' + output_dir, | 537 'output_dir=' + output_dir, |
396 '-G', | 538 '-G', |
397 'config=' + config, | 539 'config=' + config, |
398 ] | 540 ] |
399 for d in shlex.split(gyp_defines): | 541 for d in shlex.split(gyp_defines): |
400 cmd += ['-D', d] | 542 cmd += ['-D', d] |
401 return cmd | 543 return cmd |
402 | 544 |
403 def RunGNAnalyze(self, _vals): | 545 def RunGNAnalyze(self, _vals): |
404 inp = self.GetAnalyzeInput() | 546 inp = self.ReadInputJSON(['files', 'targets']) |
405 if self.args.verbose: | 547 if self.args.verbose: |
406 self.Print() | 548 self.Print() |
407 self.Print('analyze input:') | 549 self.Print('analyze input:') |
408 self.PrintJSON(inp) | 550 self.PrintJSON(inp) |
409 self.Print() | 551 self.Print() |
410 | 552 |
411 output_path = self.args.output_path[0] | 553 output_path = self.args.output_path[0] |
412 | 554 |
413 # Bail out early if a GN file was modified, since 'gn refs' won't know | 555 # Bail out early if a GN file was modified, since 'gn refs' won't know |
414 # what to do about it. | 556 # what to do about it. |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
473 | 615 |
474 if not ret and self.args.verbose: | 616 if not ret and self.args.verbose: |
475 outp = json.loads(self.ReadFile(output_path)) | 617 outp = json.loads(self.ReadFile(output_path)) |
476 self.Print() | 618 self.Print() |
477 self.Print('analyze output:') | 619 self.Print('analyze output:') |
478 self.PrintJSON(outp) | 620 self.PrintJSON(outp) |
479 self.Print() | 621 self.Print() |
480 | 622 |
481 return 0 | 623 return 0 |
482 | 624 |
483 def GetAnalyzeInput(self): | 625 def ReadInputJSON(self, required_keys): |
484 path = self.args.input_path[0] | 626 path = self.args.input_path[0] |
485 output_path = self.args.output_path[0] | 627 output_path = self.args.output_path[0] |
486 if not self.Exists(path): | 628 if not self.Exists(path): |
487 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path) | 629 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path) |
488 | 630 |
489 try: | 631 try: |
490 inp = json.loads(self.ReadFile(path)) | 632 inp = json.loads(self.ReadFile(path)) |
491 except Exception as e: | 633 except Exception as e: |
492 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' % | 634 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' % |
493 (path, e), output_path) | 635 (path, e), output_path) |
494 if not 'files' in inp: | 636 |
495 self.WriteFailureAndRaise('input file is missing a "files" key', | 637 for k in required_keys: |
496 output_path) | 638 if not k in inp: |
497 if not 'targets' in inp: | 639 self.WriteFailureAndRaise('input file is missing a "%s" key' % k, |
498 self.WriteFailureAndRaise('input file is missing a "targets" key', | 640 output_path) |
499 output_path) | |
500 | 641 |
501 return inp | 642 return inp |
502 | 643 |
503 def WriteFailureAndRaise(self, msg, path): | 644 def WriteFailureAndRaise(self, msg, path): |
504 self.WriteJSON({'error': msg}, path) | 645 self.WriteJSON({'error': msg}, path) |
505 raise MBErr(msg) | 646 raise MBErr(msg) |
506 | 647 |
507 def WriteJSON(self, obj, path): | 648 def WriteJSON(self, obj, path): |
508 try: | 649 try: |
509 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n') | 650 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n') |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
576 | 717 |
577 if __name__ == '__main__': | 718 if __name__ == '__main__': |
578 try: | 719 try: |
579 sys.exit(main(sys.argv[1:])) | 720 sys.exit(main(sys.argv[1:])) |
580 except MBErr as e: | 721 except MBErr as e: |
581 print(e) | 722 print(e) |
582 sys.exit(1) | 723 sys.exit(1) |
583 except KeyboardInterrupt: | 724 except KeyboardInterrupt: |
584 print("interrupted, exiting", stream=sys.stderr) | 725 print("interrupted, exiting", stream=sys.stderr) |
585 sys.exit(130) | 726 sys.exit(130) |
OLD | NEW |