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 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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) |
| OLD | NEW |