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, | |
| 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 return 0 # .isolated files are generated during the compile in GYP. | |
|
M-A Ruel
2015/06/03 15:47:18
No, the .isolated.gen.json files are generated dur
Dirk Pranke
2015/06/03 17:15:49
Will update.
| |
| 152 raise MBErr('Unknown meta-build type "%s"' % vals['type']) | |
| 153 | |
| 132 def CmdLookup(self): | 154 def CmdLookup(self): |
| 133 vals = self.GetConfig() | 155 vals = self.GetConfig() |
| 134 if vals['type'] == 'gn': | 156 if vals['type'] == 'gn': |
| 135 cmd = self.GNCmd('gen', '<path>', vals['gn_args']) | 157 cmd = self.GNCmd('gen', '<path>', vals['gn_args']) |
| 136 elif vals['type'] == 'gyp': | 158 elif vals['type'] == 'gyp': |
| 137 cmd = self.GYPCmd('<path>', vals['gyp_defines'], vals['gyp_config']) | 159 cmd = self.GYPCmd('<path>', vals['gyp_defines'], vals['gyp_config']) |
| 138 else: | 160 else: |
| 139 raise MBErr('Unknown meta-build type "%s"' % vals['type']) | 161 raise MBErr('Unknown meta-build type "%s"' % vals['type']) |
| 140 | 162 |
| 141 self.PrintCmd(cmd) | 163 self.PrintCmd(cmd) |
| (...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 360 ret, _, _ = self.Run(cmd) | 382 ret, _, _ = self.Run(cmd) |
| 361 if not ret and self.args.verbose: | 383 if not ret and self.args.verbose: |
| 362 outp = json.loads(self.ReadFile(self.args.output_path[0])) | 384 outp = json.loads(self.ReadFile(self.args.output_path[0])) |
| 363 self.Print() | 385 self.Print() |
| 364 self.Print('analyze output:') | 386 self.Print('analyze output:') |
| 365 self.PrintJSON(inp) | 387 self.PrintJSON(inp) |
| 366 self.Print() | 388 self.Print() |
| 367 | 389 |
| 368 return ret | 390 return ret |
| 369 | 391 |
| 392 def RunGNIsolate(self, vals): | |
| 393 build_path = self.args.path[0] | |
| 394 inp = self.GetIsolateInput() | |
| 395 if self.args.verbose: | |
| 396 self.Print() | |
| 397 self.Print('isolate input:') | |
| 398 self.PrintJSON(inp) | |
| 399 self.Print() | |
| 400 output_path = self.args.output_path[0] | |
| 401 | |
| 402 for target in inp['targets']: | |
| 403 runtime_deps_path = self.ToAbsPath(build_path, target + '.runtime_deps') | |
| 404 | |
| 405 if not self.Exists(runtime_deps_path): | |
| 406 self.WriteFailureAndRaise('"%s" does not exist' % runtime_deps_path, | |
| 407 output_path) | |
| 408 | |
| 409 command, extra_files, read_only = self.GetIsolateCommand(target, vals) | |
| 410 | |
| 411 runtime_deps = self.ReadFile(runtime_deps_path).splitlines() | |
| 412 | |
| 413 | |
| 414 isolate_path = self.ToAbsPath(build_path, target + '.isolate') | |
| 415 self.WriteFile(isolate_path, | |
| 416 pprint.pformat({ | |
| 417 'variables': { | |
| 418 'command': command, | |
| 419 'files': sorted(runtime_deps + extra_files), | |
| 420 'read_only': read_only, | |
| 421 } | |
| 422 }) + '\n') | |
| 423 | |
| 424 self.WriteJSON( | |
| 425 { | |
| 426 'args': [ | |
| 427 '--isolated', | |
| 428 self.ToSrcRelPath('%s/%s.isolated' % (build_path, target)), | |
| 429 '--isolate', | |
| 430 self.ToSrcRelPath('%s/%s.isolate' % (build_path, target)), | |
| 431 ], | |
| 432 'dir': self.chromium_src_dir, | |
| 433 'version': 1, | |
| 434 }, | |
| 435 isolate_path + '.gen.json', | |
| 436 ) | |
| 437 | |
| 438 return 0 | |
| 439 | |
| 440 def GetIsolateInput(self): | |
|
M-A Ruel
2015/06/03 15:47:19
ReadIsolateInput ?
And it's not really an isolate,
Dirk Pranke
2015/06/03 17:15:50
It was called GetIsolateInput because we're gettin
| |
| 441 path = self.args.input_path[0] | |
| 442 output_path = self.args.output_path[0] | |
| 443 if not self.Exists(path): | |
| 444 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path) | |
| 445 | |
| 446 try: | |
| 447 inp = json.loads(self.ReadFile(path)) | |
| 448 except Exception as e: | |
| 449 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' % | |
| 450 (path, e), output_path) | |
| 451 if not 'targets' in inp: | |
| 452 self.WriteFailureAndRaise('input file is missing a "targets" key', | |
| 453 output_path) | |
| 454 | |
| 455 return inp | |
| 456 | |
| 457 def GetIsolateCommand(self, target, vals): | |
|
Dirk Pranke
2015/06/03 02:24:54
This whole function is dodgy and needs to be repla
| |
| 458 output_path = self.args.output_path[0] | |
| 459 | |
| 460 extra_files = [] | |
| 461 | |
| 462 # TODO(dpranke): We should probably pull this from | |
|
M-A Ruel
2015/06/03 15:47:18
Yes but obviously this only means that it works wh
Dirk Pranke
2015/06/03 17:15:49
Well, it would only work w/ things using the files
| |
| 463 # the test list info in //testing/buildbot/*.json, | |
| 464 # and assert that the test has can_use_on_swarming_builders: True, | |
| 465 # but we hardcode it here for now. | |
| 466 test_type = {}.get(target, 'gtest_test') | |
| 467 read_only = {}.get(target, 1) | |
|
M-A Ruel
2015/06/03 15:47:18
The only test that can't run read_only is unit_tes
Dirk Pranke
2015/06/03 17:15:49
Ok, I'll keep that in mind.
| |
| 468 | |
| 469 # This needs to mirror the settings in //build/config/ui.gni: | |
| 470 # use_x11 = is_linux && !use_ozone. | |
| 471 # TODO(dpranke): Figure out how to keep this in sync better. | |
| 472 use_x11 = (sys.platform == 'linux2' and | |
| 473 not 'target_os="android"' in vals['gn_args'] and | |
| 474 not 'use_ozone=true' in vals['gn_args']) | |
| 475 | |
| 476 asan = 'is_asan=true' in vals['gn_args'] | |
| 477 msan = 'is_msan=true' in vals['gn_args'] | |
| 478 tsan = 'is_tsan=true' in vals['gn_args'] | |
| 479 | |
| 480 executable_suffix = '.exe' if sys.platform == 'win32' else '' | |
| 481 | |
| 482 if test_type == 'gtest_test': | |
| 483 extra_files.append('../../testing/test_env.py') | |
| 484 | |
| 485 if use_x11: | |
| 486 extra_files.append('xdisplaycheck') | |
| 487 extra_files.append('../../testing/xvfb.py') | |
| 488 | |
| 489 cmdline = [ | |
| 490 '../../testing/xvfb.py', | |
| 491 '.', | |
| 492 './' + str(target), | |
| 493 '--brave-new-test-launcher', | |
| 494 '--test-launcher-bot-mode', | |
| 495 '--asan=%d' % asan, | |
| 496 '--msan=%d' % msan, | |
| 497 '--tsan=%d' % tsan, | |
| 498 ] | |
| 499 else: | |
| 500 cmdline = [ | |
| 501 '../../testing/test_env.py', | |
| 502 '.', | |
| 503 './' + str(target) + executable_suffix, | |
| 504 '--brave-new-test-launcher', | |
| 505 '--test-launcher-bot-mode', | |
| 506 '--asan=%d' % asan, | |
| 507 '--msan=%d' % msan, | |
| 508 '--tsan=%d' % tsan, | |
| 509 ] | |
| 510 else: | |
| 511 # TODO(dpranke): Handle script_tests and other types of | |
| 512 # swarmed tests. | |
| 513 self.WriteFailureAndRaise('unknown test type "%s" for %s' % | |
| 514 (test_type, target), | |
| 515 output_path) | |
| 516 | |
| 517 | |
| 518 return cmdline, extra_files, read_only | |
| 519 | |
| 520 def ToAbsPath(self, build_path, relpath): | |
| 521 return os.path.join(self.chromium_src_dir, | |
| 522 self.ToSrcRelPath(build_path), | |
| 523 relpath) | |
| 524 | |
| 370 def ToSrcRelPath(self, path): | 525 def ToSrcRelPath(self, path): |
| 371 """Returns a relative path from the top of the repo.""" | 526 """Returns a relative path from the top of the repo.""" |
| 372 # TODO: Support normal paths in addition to source-absolute paths. | 527 # TODO: Support normal paths in addition to source-absolute paths. |
| 373 assert(path.startswith('//')) | 528 assert(path.startswith('//')) |
| 374 return path[2:] | 529 return path[2:] |
| 375 | 530 |
| 376 def ParseGYPConfigPath(self, path): | 531 def ParseGYPConfigPath(self, path): |
| 377 rpath = self.ToSrcRelPath(path) | 532 rpath = self.ToSrcRelPath(path) |
| 378 output_dir, _, config = rpath.rpartition('/') | 533 output_dir, _, config = rpath.rpartition('/') |
| 379 self.CheckGYPConfigIsSupported(config, path) | 534 self.CheckGYPConfigIsSupported(config, path) |
| (...skipping 196 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 576 | 731 |
| 577 if __name__ == '__main__': | 732 if __name__ == '__main__': |
| 578 try: | 733 try: |
| 579 sys.exit(main(sys.argv[1:])) | 734 sys.exit(main(sys.argv[1:])) |
| 580 except MBErr as e: | 735 except MBErr as e: |
| 581 print(e) | 736 print(e) |
| 582 sys.exit(1) | 737 sys.exit(1) |
| 583 except KeyboardInterrupt: | 738 except KeyboardInterrupt: |
| 584 print("interrupted, exiting", stream=sys.stderr) | 739 print("interrupted, exiting", stream=sys.stderr) |
| 585 sys.exit(130) | 740 sys.exit(130) |
| OLD | NEW |