Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(70)

Side by Side Diff: tools/mb/mb.py

Issue 1168513006: Make swarming work w/ GN (kinda) (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: update w/ review feedback, gn fixes Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« base/BUILD.gn ('K') | « base/BUILD.gn ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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)
OLDNEW
« base/BUILD.gn ('K') | « base/BUILD.gn ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698