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

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

Issue 1074583002: Testing and bugfixing for the new MB gyp/gn wrapper. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: clarify comments Created 5 years, 8 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
« no previous file with comments | « tools/mb/docs/user_guide.md ('k') | tools/mb/mb_config.pyl » ('j') | 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 shlex 19 import shlex
20 import shutil 20 import shutil
21 import sys 21 import sys
22 import subprocess 22 import subprocess
23 23
24 24
25 def main(args): 25 def main(args):
26 mb = MetaBuildWrapper() 26 mbw = MetaBuildWrapper()
27 mb.ParseArgs(args) 27 mbw.ParseArgs(args)
28 return mb.args.func() 28 return mbw.args.func()
29 29
30 30
31 class MetaBuildWrapper(object): 31 class MetaBuildWrapper(object):
32 def __init__(self): 32 def __init__(self):
33 p = os.path 33 p = os.path
34 d = os.path.dirname 34 d = os.path.dirname
35 self.chromium_src_dir = p.normpath(d(d(d(p.abspath(__file__))))) 35 self.chromium_src_dir = p.normpath(d(d(d(p.abspath(__file__)))))
36 self.default_config = p.join(self.chromium_src_dir, 'tools', 'mb', 36 self.default_config = p.join(self.chromium_src_dir, 'tools', 'mb',
37 'mb_config.pyl') 37 'mb_config.pyl')
38 self.args = argparse.Namespace() 38 self.args = argparse.Namespace()
(...skipping 238 matching lines...) Expand 10 before | Expand all | Expand 10 after
277 } 277 }
278 278
279 visited = [] 279 visited = []
280 self.FlattenMixins(mixins, vals, visited) 280 self.FlattenMixins(mixins, vals, visited)
281 return vals 281 return vals
282 282
283 def FlattenMixins(self, mixins, vals, visited): 283 def FlattenMixins(self, mixins, vals, visited):
284 for m in mixins: 284 for m in mixins:
285 if m not in self.mixins: 285 if m not in self.mixins:
286 raise MBErr('Unknown mixin "%s"' % m) 286 raise MBErr('Unknown mixin "%s"' % m)
287 if m in visited: 287
288 raise MBErr('Cycle in mixins for "%s": %s' % (m, visited)) 288 # TODO: check for cycles in mixins.
Dirk Pranke 2015/04/08 21:37:31 This logic didn't actually work right; it would er
289 289
290 visited.append(m) 290 visited.append(m)
291 291
292 mixin_vals = self.mixins[m] 292 mixin_vals = self.mixins[m]
293 if 'type' in mixin_vals: 293 if 'type' in mixin_vals:
294 vals['type'] = mixin_vals['type'] 294 vals['type'] = mixin_vals['type']
295 if 'gn_args' in mixin_vals: 295 if 'gn_args' in mixin_vals:
296 if vals['gn_args']: 296 if vals['gn_args']:
297 vals['gn_args'] += ' ' + mixin_vals['gn_args'] 297 vals['gn_args'] += ' ' + mixin_vals['gn_args']
298 else: 298 else:
(...skipping 10 matching lines...) Expand all
309 return vals 309 return vals
310 310
311 def RunGNGen(self, path, vals): 311 def RunGNGen(self, path, vals):
312 cmd = self.GNCmd(path, vals['gn_args']) 312 cmd = self.GNCmd(path, vals['gn_args'])
313 ret, _, _ = self.Run(cmd) 313 ret, _, _ = self.Run(cmd)
314 return ret 314 return ret
315 315
316 def GNCmd(self, path, gn_args): 316 def GNCmd(self, path, gn_args):
317 # TODO(dpranke): Find gn explicitly in the path ... 317 # TODO(dpranke): Find gn explicitly in the path ...
318 cmd = ['gn', 'gen', path] 318 cmd = ['gn', 'gen', path]
319 gn_args = gn_args.replace("$(goma_dir)", gn_args) 319 gn_args = gn_args.replace("$(goma_dir)", self.args.goma_dir)
320 if gn_args: 320 if gn_args:
321 cmd.append('--args=%s' % gn_args) 321 cmd.append('--args=%s' % gn_args)
322 return cmd 322 return cmd
323 323
324 def RunGYPGen(self, path, vals): 324 def RunGYPGen(self, path, vals):
325 output_dir, gyp_config = self.ParseGYPConfigPath(path) 325 output_dir, gyp_config = self.ParseGYPConfigPath(path)
326 if gyp_config != vals['gyp_config']: 326 if gyp_config != vals['gyp_config']:
327 raise MBErr('The last component of the path (%s) must match the ' 327 raise MBErr('The last component of the path (%s) must match the '
328 'GYP configuration specified in the config (%s), and ' 328 'GYP configuration specified in the config (%s), and '
329 'it does not.' % (gyp_config, vals['gyp_config'])) 329 'it does not.' % (gyp_config, vals['gyp_config']))
330 cmd = self.GYPCmd(output_dir, vals['gyp_defines'], config=gyp_config) 330 cmd = self.GYPCmd(output_dir, vals['gyp_defines'], config=gyp_config)
331 ret, _, _ = self.Run(cmd) 331 ret, _, _ = self.Run(cmd)
332 return ret 332 return ret
333 333
334 def RunGYPAnalyze(self, vals): 334 def RunGYPAnalyze(self, vals):
335 output_dir, gyp_config = self.ParseGYPConfigPath(self.args.path[0]) 335 output_dir, gyp_config = self.ParseGYPConfigPath(self.args.path[0])
336 if gyp_config != vals['gyp_config']: 336 if gyp_config != vals['gyp_config']:
337 raise MBErr('The last component of the path (%s) must match the ' 337 raise MBErr('The last component of the path (%s) must match the '
338 'GYP configuration specified in the config (%s), and ' 338 'GYP configuration specified in the config (%s), and '
339 'it does not.' % (gyp_config, vals['gyp_config'])) 339 'it does not.' % (gyp_config, vals['gyp_config']))
340 cmd = self.GYPCmd(output_dir, vals['gyp_defines'], config=gyp_config) 340 cmd = self.GYPCmd(output_dir, vals['gyp_defines'], config=gyp_config)
341 cmd.extend(['-G', 'config_path=%s' % self.args.input_path[0], 341 cmd.extend(['-G', 'config_path=%s' % self.args.input_path[0],
342 '-G', 'analyzer_output_path=%s' % self.args.output_path[0]]) 342 '-G', 'analyzer_output_path=%s' % self.args.output_path[0]])
343 ret, _, _ = self.Run(cmd) 343 ret, _, _ = self.Run(cmd)
344 return ret 344 return ret
345 345
346 def ParseGYPOutputPath(self, path): 346 def ToSrcRelPath(self, path):
347 """Returns a relative path from the top of the repo."""
348 # TODO: Support normal paths in addition to source-absolute paths.
347 assert(path.startswith('//')) 349 assert(path.startswith('//'))
348 return path[2:] 350 return path[2:]
349 351
350 def ParseGYPConfigPath(self, path): 352 def ParseGYPConfigPath(self, path):
351 assert(path.startswith('//')) 353 rpath = self.ToSrcRelPath(path)
352 output_dir, _, config = path[2:].rpartition('/') 354 output_dir, _, config = rpath.rpartition('/')
353 self.CheckGYPConfigIsSupported(config, path) 355 self.CheckGYPConfigIsSupported(config, path)
354 return output_dir, config 356 return output_dir, config
355 357
356 def CheckGYPConfigIsSupported(self, config, path): 358 def CheckGYPConfigIsSupported(self, config, path):
357 if config not in ('Debug', 'Release'): 359 if config not in ('Debug', 'Release'):
358 if (sys.platform in ('win32', 'cygwin') and 360 if (sys.platform in ('win32', 'cygwin') and
359 config not in ('Debug_x64', 'Release_x64')): 361 config not in ('Debug_x64', 'Release_x64')):
360 raise MBErr('Unknown or unsupported config type "%s" in "%s"' % 362 raise MBErr('Unknown or unsupported config type "%s" in "%s"' %
361 config, path) 363 config, path)
362 364
(...skipping 19 matching lines...) Expand all
382 if any(f.endswith('.gn') or f.endswith('.gni') for f in inp['files']): 384 if any(f.endswith('.gn') or f.endswith('.gni') for f in inp['files']):
383 self.WriteJSONOutput({'status': 'Found dependency (all)'}) 385 self.WriteJSONOutput({'status': 'Found dependency (all)'})
384 return 0 386 return 0
385 387
386 # TODO: Break long lists of files that might exceed the max command line 388 # TODO: Break long lists of files that might exceed the max command line
387 # up into chunks so that we can return more accurate info. 389 # up into chunks so that we can return more accurate info.
388 if len(' '.join(inp['files'])) > 1024: 390 if len(' '.join(inp['files'])) > 1024:
389 self.WriteJSONOutput({'status': 'Found dependency (all)'}) 391 self.WriteJSONOutput({'status': 'Found dependency (all)'})
390 return 0 392 return 0
391 393
392 cmd = (['gn', 'refs', self.args.path[0]] + inp['files'] + 394 cmd = (['gn', 'refs', self.args.path[0] ] +
395 ['//' + f for f in inp['files']] +
393 ['--type=executable', '--all', '--as=output']) 396 ['--type=executable', '--all', '--as=output'])
394 needed_targets = [] 397 needed_targets = []
395 ret, out, _ = self.Run(cmd) 398 ret, out, _ = self.Run(cmd)
396
397 if ret: 399 if ret:
398 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out)) 400 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out))
399 401
400 rpath = os.path.relpath(self.args.path[0], self.chromium_src_dir) + os.sep 402 rpath = self.ToSrcRelPath(self.args.path[0]) + os.sep
401 needed_targets = [t.replace(rpath, '') for t in out.splitlines()] 403 needed_targets = [t.replace(rpath, '') for t in out.splitlines()]
402 needed_targets = [nt for nt in needed_targets if nt in inp['targets']] 404 needed_targets = [nt for nt in needed_targets if nt in inp['targets']]
403 405
404 for nt in needed_targets:
405 self.Print(nt)
406
407 if needed_targets: 406 if needed_targets:
408 # TODO: it could be that a target X might depend on a target Y 407 # TODO: it could be that a target X might depend on a target Y
409 # and both would be listed in the input, but we would only need 408 # and both would be listed in the input, but we would only need
410 # to specify target X as a build_target (whereas both X and Y are 409 # to specify target X as a build_target (whereas both X and Y are
411 # targets). I'm not sure if that optimization is generally worth it. 410 # targets). I'm not sure if that optimization is generally worth it.
412 self.WriteJSON({'targets': needed_targets, 411 self.WriteJSON({'targets': needed_targets,
413 'build_targets': needed_targets, 412 'build_targets': needed_targets,
414 'status': 'Found dependency'}) 413 'status': 'Found dependency'})
415 else: 414 else:
416 self.WriteJSON({'targets': [], 415 self.WriteJSON({'targets': [],
(...skipping 20 matching lines...) Expand all
437 return inp 436 return inp
438 437
439 def WriteFailureAndRaise(self, msg): 438 def WriteFailureAndRaise(self, msg):
440 self.WriteJSON({'error': msg}) 439 self.WriteJSON({'error': msg})
441 raise MBErr(msg) 440 raise MBErr(msg)
442 441
443 def WriteJSON(self, obj): 442 def WriteJSON(self, obj):
444 output_path = self.args.output_path[0] 443 output_path = self.args.output_path[0]
445 if output_path: 444 if output_path:
446 try: 445 try:
447 self.WriteFile(output_path, json.dumps(obj)) 446 self.WriteFile(output_path, json.dumps(obj, indent=2) + '\n')
448 except Exception as e: 447 except Exception as e:
449 raise MBErr('Error %s writing to the output path "%s"' % 448 raise MBErr('Error %s writing to the output path "%s"' %
450 (e, output_path)) 449 (e, output_path))
451 450
452 def PrintCmd(self, cmd): 451 def PrintCmd(self, cmd):
453 if cmd[0] == sys.executable: 452 if cmd[0] == sys.executable:
454 cmd = ['python'] + cmd[1:] 453 cmd = ['python'] + cmd[1:]
455 self.Print(*[pipes.quote(c) for c in cmd]) 454 self.Print(*[pipes.quote(c) for c in cmd])
456 455
457 def Print(self, *args, **kwargs): 456 def Print(self, *args, **kwargs):
458 # This function largely exists so it can be overridden for testing. 457 # This function largely exists so it can be overridden for testing.
459 print(*args, **kwargs) 458 print(*args, **kwargs)
460 459
461 def Run(self, cmd): 460 def Run(self, cmd):
462 # This function largely exists so it can be overridden for testing. 461 # This function largely exists so it can be overridden for testing.
463 if self.args.dryrun or self.args.verbose: 462 if self.args.dryrun or self.args.verbose:
464 self.PrintCmd(cmd) 463 self.PrintCmd(cmd)
465 if self.args.dryrun: 464 if self.args.dryrun:
466 return 0, '', '' 465 return 0, '', ''
467 ret, out, err = self.Call(cmd) 466 ret, out, err = self.Call(cmd)
468 if self.args.verbose: 467 if self.args.verbose:
469 if out: 468 if out:
470 self.Print(out) 469 self.Print(out, end='')
471 if err: 470 if err:
472 self.Print(err, file=sys.stderr) 471 self.Print(err, end='', file=sys.stderr)
473 return ret, out, err 472 return ret, out, err
474 473
475 def Call(self, cmd): 474 def Call(self, cmd):
476 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir, 475 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
477 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 476 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
478 out, err = p.communicate() 477 out, err = p.communicate()
479 return p.returncode, out, err 478 return p.returncode, out, err
480 479
481 def ExpandUser(self, path): 480 def ExpandUser(self, path):
482 # This function largely exists so it can be overridden for testing. 481 # This function largely exists so it can be overridden for testing.
(...skipping 19 matching lines...) Expand all
502 501
503 if __name__ == '__main__': 502 if __name__ == '__main__':
504 try: 503 try:
505 sys.exit(main(sys.argv[1:])) 504 sys.exit(main(sys.argv[1:]))
506 except MBErr as e: 505 except MBErr as e:
507 print(e) 506 print(e)
508 sys.exit(1) 507 sys.exit(1)
509 except KeyboardInterrupt: 508 except KeyboardInterrupt:
510 print("interrupted, exiting", stream=sys.stderr) 509 print("interrupted, exiting", stream=sys.stderr)
511 sys.exit(130) 510 sys.exit(130)
OLDNEW
« no previous file with comments | « tools/mb/docs/user_guide.md ('k') | tools/mb/mb_config.pyl » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698