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 """ |
| 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 |