Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2015 The LUCI Authors. All rights reserved. | 2 # Copyright 2015 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
| 5 | 5 |
| 6 """Tool to interact with recipe repositories. | 6 """Tool to interact with recipe repositories. |
| 7 | 7 |
| 8 This tool operates on the nearest ancestor directory containing an | 8 This tool operates on the nearest ancestor directory containing an |
| 9 infra/config/recipes.cfg. | 9 infra/config/recipes.cfg. |
| 10 """ | 10 """ |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 26 | 26 |
| 27 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) | 27 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 28 sys.path.insert(0, ROOT_DIR) | 28 sys.path.insert(0, ROOT_DIR) |
| 29 | 29 |
| 30 from recipe_engine import env | 30 from recipe_engine import env |
| 31 from recipe_engine import arguments_pb2 | 31 from recipe_engine import arguments_pb2 |
| 32 from recipe_engine import util as recipe_util | 32 from recipe_engine import util as recipe_util |
| 33 from google.protobuf import json_format as jsonpb | 33 from google.protobuf import json_format as jsonpb |
| 34 | 34 |
| 35 | 35 |
| 36 def test(config_file, package_deps, args, op_args): | 36 def test(config_file, package_deps, args): |
| 37 try: | 37 try: |
| 38 from recipe_engine import test | 38 from recipe_engine import test |
| 39 except ImportError: | 39 except ImportError: |
| 40 logging.error( | 40 logging.error( |
| 41 'Error while importing testing libraries. You may be missing the pip' | 41 'Error while importing testing libraries. You may be missing the pip' |
| 42 ' package "coverage". Install it, or use the --use-bootstrap command' | 42 ' package "coverage". Install it, or use the --use-bootstrap command' |
| 43 ' line argument when calling into the recipe engine, which will install' | 43 ' line argument when calling into the recipe engine, which will install' |
| 44 ' it for you.') | 44 ' it for you.') |
| 45 raise | 45 raise |
| 46 | 46 |
| 47 from recipe_engine import loader | 47 from recipe_engine import loader |
| 48 from recipe_engine import package | 48 from recipe_engine import package |
| 49 | 49 |
| 50 universe = loader.RecipeUniverse(package_deps, config_file) | 50 universe = loader.RecipeUniverse(package_deps, config_file) |
| 51 universe_view = loader.UniverseView(universe, package_deps.root_package) | 51 universe_view = loader.UniverseView(universe, package_deps.root_package) |
| 52 | 52 |
| 53 # Prevent flakiness caused by stale pyc files. | 53 # Prevent flakiness caused by stale pyc files. |
| 54 package.cleanup_pyc(package_deps.root_package.recipes_dir) | 54 package.cleanup_pyc(package_deps.root_package.recipes_dir) |
| 55 | 55 |
| 56 return test.main( | 56 return test.main( |
| 57 universe_view, raw_args=args.args, | 57 universe_view, raw_args=args.args, |
| 58 engine_flags=op_args.engine_flags) | 58 engine_flags=args.operational_args.engine_flags) |
| 59 | 59 |
| 60 | 60 |
| 61 def lint(config_file, package_deps, args): | 61 def lint(config_file, package_deps, args): |
| 62 from recipe_engine import lint_test | 62 from recipe_engine import lint_test |
| 63 from recipe_engine import loader | 63 from recipe_engine import loader |
| 64 | 64 |
| 65 universe = loader.RecipeUniverse(package_deps, config_file) | 65 universe = loader.RecipeUniverse(package_deps, config_file) |
| 66 universe_view = loader.UniverseView(universe, package_deps.root_package) | 66 universe_view = loader.UniverseView(universe, package_deps.root_package) |
| 67 | 67 |
| 68 lint_test.main(universe_view, args.whitelist or []) | 68 lint_test.main(universe_view, args.whitelist or []) |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 141 | 141 |
| 142 with stream_engine.make_step_stream('Failure reason') as s: | 142 with stream_engine.make_step_stream('Failure reason') as s: |
| 143 with s.new_log_stream('reason') as l: | 143 with s.new_log_stream('reason') as l: |
| 144 l.write_split(f.human_reason) | 144 l.write_split(f.human_reason) |
| 145 | 145 |
| 146 return 1 | 146 return 1 |
| 147 | 147 |
| 148 return 0 | 148 return 0 |
| 149 | 149 |
| 150 | 150 |
| 151 def run(config_file, package_deps, args, op_args): | 151 def run(config_file, package_deps, args): |
| 152 from recipe_engine import run as recipe_run | 152 from recipe_engine import run as recipe_run |
| 153 from recipe_engine import loader | 153 from recipe_engine import loader |
| 154 from recipe_engine import step_runner | 154 from recipe_engine import step_runner |
| 155 from recipe_engine import stream | 155 from recipe_engine import stream |
| 156 from recipe_engine import stream_logdog | 156 from recipe_engine import stream_logdog |
| 157 | 157 |
| 158 if args.props: | 158 if args.props: |
| 159 for p in args.props: | 159 for p in args.props: |
| 160 args.properties.update(p) | 160 args.properties.update(p) |
| 161 | 161 |
| 162 def get_properties_from_operational_args(op_args): | 162 def get_properties_from_operational_args(op_args): |
| 163 if not op_args.properties.property: | 163 if not op_args.properties.property: |
| 164 return None | 164 return None |
| 165 return _op_properties_to_dict(op_args.properties.property) | 165 return _op_properties_to_dict(op_args.properties.property) |
| 166 | 166 |
| 167 op_args = args.operational_args | |
| 167 op_properties = get_properties_from_operational_args(op_args) | 168 op_properties = get_properties_from_operational_args(op_args) |
| 168 if args.properties and op_properties: | 169 if args.properties and op_properties: |
| 169 raise ValueError( | 170 raise ValueError( |
| 170 "Got operational args properties as well as CLI properties.") | 171 "Got operational args properties as well as CLI properties.") |
| 171 | 172 |
| 172 properties = op_properties | 173 properties = op_properties |
| 173 if not properties: | 174 if not properties: |
| 174 properties = args.properties | 175 properties = args.properties |
| 175 | 176 |
| 176 properties['recipe'] = args.recipe | 177 properties['recipe'] = args.recipe |
| (...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 376 help='Override a project repository path with a local one.') | 377 help='Override a project repository path with a local one.') |
| 377 parser.add_argument( | 378 parser.add_argument( |
| 378 # Use "None" as default so that we can recognize when none of the | 379 # Use "None" as default so that we can recognize when none of the |
| 379 # bootstrap options were passed. | 380 # bootstrap options were passed. |
| 380 '--use-bootstrap', action='store_true', default=None, | 381 '--use-bootstrap', action='store_true', default=None, |
| 381 help='Use bootstrap/bootstrap.py to create a isolated python virtualenv' | 382 help='Use bootstrap/bootstrap.py to create a isolated python virtualenv' |
| 382 ' with required python dependencies.') | 383 ' with required python dependencies.') |
| 383 parser.add_argument( | 384 parser.add_argument( |
| 384 '--disable-bootstrap', action='store_false', dest='use_bootstrap', | 385 '--disable-bootstrap', action='store_false', dest='use_bootstrap', |
| 385 help='Disables bootstrap (see --use-bootstrap)') | 386 help='Disables bootstrap (see --use-bootstrap)') |
| 387 | |
| 388 def operational_args_type(value): | |
| 389 op_args = arguments_pb2.Arguments() | |
| 390 with open(value) as fd: | |
|
dnj
2017/04/26 19:52:04
nit: Up to you, but if you care you could make thi
iannucci
2017/04/26 20:08:35
oh, yeah I just transplanted this code without loo
| |
| 391 data = fd.read() | |
| 392 jsonpb.Parse(data, op_args) | |
| 393 return op_args | |
| 394 | |
| 386 parser.add_argument( | 395 parser.add_argument( |
| 387 '--operational-args-path', action='store', | 396 '--operational-args-path', |
| 388 type=os.path.abspath, | 397 dest='operational_args', |
| 398 type=operational_args_type, | |
| 389 help='The path to an operational Arguments file. If provided, this file ' | 399 help='The path to an operational Arguments file. If provided, this file ' |
| 390 'must contain a JSONPB-encoded Arguments protobuf message, and will ' | 400 'must contain a JSONPB-encoded Arguments protobuf message, and will ' |
| 391 'be integrated into the runtime parameters.') | 401 'be integrated into the runtime parameters.') |
| 392 | 402 |
| 393 | 403 |
| 394 def post_process_common_args(parser, args): | 404 def post_process_common_args(parser, args): |
| 395 if args.command == "remote": | 405 if args.command == "remote": |
| 396 # TODO(iannucci): this is a hack; remote doesn't behave like ANY other | 406 # TODO(iannucci): this is a hack; remote doesn't behave like ANY other |
| 397 # commands. A way to solve this will be to allow --package to take a remote | 407 # commands. A way to solve this will be to allow --package to take a remote |
| 398 # repo and then simply remove the remote subcommand entirely. | 408 # repo and then simply remove the remote subcommand entirely. |
| (...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 589 'with their documentation') | 599 'with their documentation') |
| 590 doc_p.add_argument('recipe', nargs='?', | 600 doc_p.add_argument('recipe', nargs='?', |
| 591 help='Restrict documentation to this recipe') | 601 help='Restrict documentation to this recipe') |
| 592 doc_p.add_argument('--kind', default='jsonpb', choices=doc_kinds, | 602 doc_p.add_argument('--kind', default='jsonpb', choices=doc_kinds, |
| 593 help='Output this kind of documentation') | 603 help='Output this kind of documentation') |
| 594 doc_p.set_defaults(command='doc') | 604 doc_p.set_defaults(command='doc') |
| 595 | 605 |
| 596 args = parser.parse_args() | 606 args = parser.parse_args() |
| 597 post_process_common_args(parser, args) | 607 post_process_common_args(parser, args) |
| 598 | 608 |
| 599 # Load/parse operational arguments. | |
| 600 op_args = arguments_pb2.Arguments() | |
| 601 if args.operational_args_path is not None: | |
| 602 with open(args.operational_args_path) as fd: | |
| 603 data = fd.read() | |
| 604 jsonpb.Parse(data, op_args) | |
| 605 | |
| 606 # TODO(iannucci): We should always do logging.basicConfig() (probably with | 609 # TODO(iannucci): We should always do logging.basicConfig() (probably with |
| 607 # logging.WARNING), even if no verbose is passed. However we need to be | 610 # logging.WARNING), even if no verbose is passed. However we need to be |
| 608 # careful as this could cause issues with spurious/unexpected output. I think | 611 # careful as this could cause issues with spurious/unexpected output. I think |
| 609 # it's risky enough to do in a different CL. | 612 # it's risky enough to do in a different CL. |
| 610 | 613 |
| 611 if args.verbose > 0: | 614 if args.verbose > 0: |
| 612 logging.basicConfig() | 615 logging.basicConfig() |
| 613 logging.getLogger().setLevel(logging.INFO) | 616 logging.getLogger().setLevel(logging.INFO) |
| 614 if args.verbose > 1: | 617 if args.verbose > 1: |
| 615 logging.getLogger().setLevel(logging.DEBUG) | 618 logging.getLogger().setLevel(logging.DEBUG) |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 668 return subprocess.call( | 671 return subprocess.call( |
| 669 [ | 672 [ |
| 670 os.path.join(env_path, 'bin', python_exe), | 673 os.path.join(env_path, 'bin', python_exe), |
| 671 '-B', # Don't compile "pyo" binaries. | 674 '-B', # Don't compile "pyo" binaries. |
| 672 '-E', # Don't use PYTHON* enviornment variables. | 675 '-E', # Don't use PYTHON* enviornment variables. |
| 673 '-s', # Don't use user 'site.py'. | 676 '-s', # Don't use user 'site.py'. |
| 674 os.path.join(ROOT_DIR, 'recipes.py'), | 677 os.path.join(ROOT_DIR, 'recipes.py'), |
| 675 ] + sys.argv[1:]) | 678 ] + sys.argv[1:]) |
| 676 | 679 |
| 677 # Standard recipe engine operation. | 680 # Standard recipe engine operation. |
| 678 return _real_main(args, op_args) | 681 return _real_main(args) |
| 679 | 682 |
| 680 finally: | 683 finally: |
| 681 # If we're using a temporary deps directory, clean it up here. | 684 # If we're using a temporary deps directory, clean it up here. |
| 682 if temp_deps_dir: | 685 if temp_deps_dir: |
| 683 logging.info('Cleaning up temporary deps path: %s', temp_deps_dir) | 686 logging.info('Cleaning up temporary deps path: %s', temp_deps_dir) |
| 684 | 687 |
| 685 # Remove as much of the temporary directory as we can. If something goes | 688 # Remove as much of the temporary directory as we can. If something goes |
| 686 # wrong, log the error, but don't actually raise anything. | 689 # wrong, log the error, but don't actually raise anything. |
| 687 def on_error(_function, path, excinfo): | 690 def on_error(_function, path, excinfo): |
| 688 logging.error('Error cleaning up temporary deps file: %s', path, | 691 logging.error('Error cleaning up temporary deps file: %s', path, |
| 689 exc_info=excinfo) | 692 exc_info=excinfo) |
| 690 shutil.rmtree(temp_deps_dir, onerror=on_error) | 693 shutil.rmtree(temp_deps_dir, onerror=on_error) |
| 691 | 694 |
| 692 | 695 |
| 693 def _real_main(args, op_args): | 696 def _real_main(args): |
| 694 from recipe_engine import package, package_io | 697 from recipe_engine import package |
| 695 | 698 |
| 696 # Commands which do not require config_file, package_deps, and other objects | 699 # Commands which do not require config_file, package_deps, and other objects |
| 697 # initialized later. | 700 # initialized later. |
| 698 if args.command == 'remote': | 701 if args.command == 'remote': |
| 699 return remote(args) | 702 return remote(args) |
| 700 | 703 |
| 701 config_file = args.package | 704 config_file = args.package |
| 702 repo_root = package.InfraRepoConfig().from_recipes_cfg(args.package.path) | 705 repo_root = package.InfraRepoConfig().from_recipes_cfg(args.package.path) |
| 703 | 706 |
| 704 try: | 707 try: |
| 705 # TODO(phajdan.jr): gracefully handle inconsistent deps when rolling. | 708 # TODO(phajdan.jr): gracefully handle inconsistent deps when rolling. |
| 706 # This fails if the starting point does not have consistent dependency | 709 # This fails if the starting point does not have consistent dependency |
| 707 # graph. When performing an automated roll, it'd make sense to attempt | 710 # graph. When performing an automated roll, it'd make sense to attempt |
| 708 # to automatically find a consistent state, rather than bailing out. | 711 # to automatically find a consistent state, rather than bailing out. |
| 709 # Especially that only some subcommands refer to package_deps. | 712 # Especially that only some subcommands refer to package_deps. |
| 710 package_deps = package.PackageDeps.create( | 713 package_deps = package.PackageDeps.create( |
| 711 repo_root, config_file, allow_fetch=not args.no_fetch, | 714 repo_root, config_file, allow_fetch=not args.no_fetch, |
| 712 deps_path=args.deps_path, overrides=args.project_override) | 715 deps_path=args.deps_path, overrides=args.project_override) |
| 713 except subprocess.CalledProcessError: | 716 except subprocess.CalledProcessError: |
| 714 # A git checkout failed somewhere. Return 2, which is the sign that this is | 717 # A git checkout failed somewhere. Return 2, which is the sign that this is |
| 715 # an infra failure, rather than a test failure. | 718 # an infra failure, rather than a test failure. |
| 716 return 2 | 719 return 2 |
| 717 | 720 |
| 718 if args.command == 'fetch': | 721 if args.command == 'fetch': |
| 719 # We already did everything in the create() call above. | 722 # We already did everything in the create() call above. |
| 720 assert not args.no_fetch, 'Fetch? No-fetch? Make up your mind!' | 723 assert not args.no_fetch, 'Fetch? No-fetch? Make up your mind!' |
| 721 return 0 | 724 return 0 |
| 722 elif args.command == 'test': | 725 elif args.command == 'test': |
| 723 return test(config_file, package_deps, args, op_args) | 726 return test(config_file, package_deps, args) |
| 724 elif args.command == 'bundle': | 727 elif args.command == 'bundle': |
| 725 return bundle(config_file, package_deps, args) | 728 return bundle(config_file, package_deps, args) |
| 726 elif args.command == 'lint': | 729 elif args.command == 'lint': |
| 727 return lint(config_file, package_deps, args) | 730 return lint(config_file, package_deps, args) |
| 728 elif args.command == 'run': | 731 elif args.command == 'run': |
| 729 return run(config_file, package_deps, args, op_args) | 732 return run(config_file, package_deps, args) |
| 730 elif args.command == 'autoroll': | 733 elif args.command == 'autoroll': |
| 731 return autoroll(repo_root, config_file, args) | 734 return autoroll(repo_root, config_file, args) |
| 732 elif args.command == 'depgraph': | 735 elif args.command == 'depgraph': |
| 733 return depgraph(config_file, package_deps, args) | 736 return depgraph(config_file, package_deps, args) |
| 734 elif args.command == 'refs': | 737 elif args.command == 'refs': |
| 735 return refs(config_file, package_deps, args) | 738 return refs(config_file, package_deps, args) |
| 736 elif args.command == 'doc': | 739 elif args.command == 'doc': |
| 737 return doc(config_file, package_deps, args) | 740 return doc(config_file, package_deps, args) |
| 738 else: | 741 else: |
| 739 print """Dear sir or madam, | 742 print """Dear sir or madam, |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 767 | 770 |
| 768 if not isinstance(ret, int): | 771 if not isinstance(ret, int): |
| 769 if ret is None: | 772 if ret is None: |
| 770 ret = 0 | 773 ret = 0 |
| 771 else: | 774 else: |
| 772 print >> sys.stderr, ret | 775 print >> sys.stderr, ret |
| 773 ret = 1 | 776 ret = 1 |
| 774 sys.stdout.flush() | 777 sys.stdout.flush() |
| 775 sys.stderr.flush() | 778 sys.stderr.flush() |
| 776 os._exit(ret) | 779 os._exit(ret) |
| OLD | NEW |