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 import tempfile |
23 | 24 |
24 | 25 |
25 def main(args): | 26 def main(args): |
26 mbw = MetaBuildWrapper() | 27 mbw = MetaBuildWrapper() |
27 mbw.ParseArgs(args) | 28 mbw.ParseArgs(args) |
28 return mbw.args.func() | 29 return mbw.args.func() |
29 | 30 |
30 | 31 |
31 class MetaBuildWrapper(object): | 32 class MetaBuildWrapper(object): |
32 def __init__(self): | 33 def __init__(self): |
(...skipping 375 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
408 self.Print() | 409 self.Print() |
409 | 410 |
410 output_path = self.args.output_path[0] | 411 output_path = self.args.output_path[0] |
411 | 412 |
412 # Bail out early if a GN file was modified, since 'gn refs' won't know | 413 # Bail out early if a GN file was modified, since 'gn refs' won't know |
413 # what to do about it. | 414 # what to do about it. |
414 if any(f.endswith('.gn') or f.endswith('.gni') for f in inp['files']): | 415 if any(f.endswith('.gn') or f.endswith('.gni') for f in inp['files']): |
415 self.WriteJSON({'status': 'Found dependency (all)'}, output_path) | 416 self.WriteJSON({'status': 'Found dependency (all)'}, output_path) |
416 return 0 | 417 return 0 |
417 | 418 |
418 # TODO: Because of the --type=executable filter below, we don't detect | 419 # Bail out early if 'all' was asked for, since 'gn refs' won't recognize it. |
419 # when files will cause 'all' or 'gn_all' or similar targets to be | 420 if 'all' in inp['targets']: |
420 # dirty. We need to figure out how to handle that properly, but for | |
421 # now we can just bail out early. | |
422 if 'gn_all' in inp['targets'] or 'all' in inp['targets']: | |
423 self.WriteJSON({'status': 'Found dependency (all)'}, output_path) | 421 self.WriteJSON({'status': 'Found dependency (all)'}, output_path) |
424 return 0 | 422 return 0 |
425 | 423 |
426 all_needed_targets = set() | |
427 ret = 0 | 424 ret = 0 |
428 for f in inp['files']: | 425 response_file = self.TempFile() |
| 426 response_file.write('\n'.join(inp['files']) + '\n') |
| 427 response_file.close() |
| 428 |
| 429 matching_targets = [] |
| 430 try: |
429 cmd = self.GNCmd('refs', self.args.path[0]) + [ | 431 cmd = self.GNCmd('refs', self.args.path[0]) + [ |
430 '//' + f, '--type=executable', '--all', '--as=output'] | 432 '@%s' % response_file.name, |
| 433 '--type=executable', '--all', '--as=output' |
| 434 ] |
431 ret, out, _ = self.Run(cmd) | 435 ret, out, _ = self.Run(cmd) |
432 if ret and not 'The input matches no targets' in out: | 436 if ret and not 'The input matches no targets' in out: |
433 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), | 437 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), |
434 output_path) | 438 output_path) |
| 439 build_dir = self.ToSrcRelPath(self.args.path[0]) + os.sep |
| 440 for output in out.splitlines(): |
| 441 build_output = output.replace(build_dir, '') |
| 442 if build_output in inp['targets']: |
| 443 matching_targets.append(build_output) |
| 444 finally: |
| 445 self.RemoveFile(response_file.name) |
435 | 446 |
436 rpath = self.ToSrcRelPath(self.args.path[0]) + os.sep | 447 if matching_targets: |
437 needed_targets = [t.replace(rpath, '') for t in out.splitlines()] | |
438 needed_targets = [nt for nt in needed_targets if nt in inp['targets']] | |
439 all_needed_targets.update(set(needed_targets)) | |
440 | |
441 if all_needed_targets: | |
442 # TODO: it could be that a target X might depend on a target Y | 448 # TODO: it could be that a target X might depend on a target Y |
443 # and both would be listed in the input, but we would only need | 449 # and both would be listed in the input, but we would only need |
444 # to specify target X as a build_target (whereas both X and Y are | 450 # to specify target X as a build_target (whereas both X and Y are |
445 # targets). I'm not sure if that optimization is generally worth it. | 451 # targets). I'm not sure if that optimization is generally worth it. |
446 self.WriteJSON({'targets': sorted(all_needed_targets), | 452 self.WriteJSON({'targets': sorted(matching_targets), |
447 'build_targets': sorted(all_needed_targets), | 453 'build_targets': sorted(matching_targets), |
448 'status': 'Found dependency'}, output_path) | 454 'status': 'Found dependency'}, output_path) |
449 else: | 455 else: |
450 self.WriteJSON({'targets': [], | 456 self.WriteJSON({'targets': [], |
451 'build_targets': [], | 457 'build_targets': [], |
452 'status': 'No dependency'}, output_path) | 458 'status': 'No dependency'}, output_path) |
453 | 459 |
454 if not ret and self.args.verbose: | 460 if not ret and self.args.verbose: |
455 outp = json.loads(self.ReadFile(output_path)) | 461 outp = json.loads(self.ReadFile(output_path)) |
456 self.Print() | 462 self.Print() |
457 self.Print('analyze output:') | 463 self.Print('analyze output:') |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
529 | 535 |
530 def Exists(self, path): | 536 def Exists(self, path): |
531 # This function largely exists so it can be overridden for testing. | 537 # This function largely exists so it can be overridden for testing. |
532 return os.path.exists(path) | 538 return os.path.exists(path) |
533 | 539 |
534 def ReadFile(self, path): | 540 def ReadFile(self, path): |
535 # This function largely exists so it can be overriden for testing. | 541 # This function largely exists so it can be overriden for testing. |
536 with open(path) as fp: | 542 with open(path) as fp: |
537 return fp.read() | 543 return fp.read() |
538 | 544 |
| 545 def RemoveFile(self, path): |
| 546 # This function largely exists so it can be overriden for testing. |
| 547 os.remove(path) |
| 548 |
| 549 def TempFile(self, mode='w'): |
| 550 # This function largely exists so it can be overriden for testing. |
| 551 return tempfile.NamedTemporaryFile(mode=mode, delete=False) |
| 552 |
539 def WriteFile(self, path, contents): | 553 def WriteFile(self, path, contents): |
540 # This function largely exists so it can be overriden for testing. | 554 # This function largely exists so it can be overriden for testing. |
541 with open(path, 'w') as fp: | 555 with open(path, 'w') as fp: |
542 return fp.write(contents) | 556 return fp.write(contents) |
543 | 557 |
| 558 |
544 class MBErr(Exception): | 559 class MBErr(Exception): |
545 pass | 560 pass |
546 | 561 |
547 | 562 |
548 if __name__ == '__main__': | 563 if __name__ == '__main__': |
549 try: | 564 try: |
550 sys.exit(main(sys.argv[1:])) | 565 sys.exit(main(sys.argv[1:])) |
551 except MBErr as e: | 566 except MBErr as e: |
552 print(e) | 567 print(e) |
553 sys.exit(1) | 568 sys.exit(1) |
554 except KeyboardInterrupt: | 569 except KeyboardInterrupt: |
555 print("interrupted, exiting", stream=sys.stderr) | 570 print("interrupted, exiting", stream=sys.stderr) |
556 sys.exit(130) | 571 sys.exit(130) |
OLD | NEW |