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 """ |
| 11 | 11 |
| 12 import argparse | 12 import argparse |
| 13 import collections | 13 import collections |
| 14 import json | 14 import json |
| 15 import logging | 15 import logging |
| 16 import os | 16 import os |
| 17 import shutil | |
| 17 import subprocess | 18 import subprocess |
| 18 import sys | 19 import sys |
| 20 import tempfile | |
| 19 | 21 |
| 20 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) | 22 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 21 sys.path.insert(0, ROOT_DIR) | 23 sys.path.insert(0, ROOT_DIR) |
| 22 | 24 |
| 23 from recipe_engine import env | 25 from recipe_engine import env |
| 24 from recipe_engine import arguments_pb2 | 26 from recipe_engine import arguments_pb2 |
| 25 from google.protobuf import json_format as jsonpb | 27 from google.protobuf import json_format as jsonpb |
| 26 from google.protobuf import text_format as textpb | 28 from google.protobuf import text_format as textpb |
| 27 | 29 |
| 28 def get_package_config(args): | 30 def get_package_config(args): |
| (...skipping 338 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 367 """Creates a properties dictionary from an arguments_pb2.PropertyMap entry. | 369 """Creates a properties dictionary from an arguments_pb2.PropertyMap entry. |
| 368 | 370 |
| 369 Args: | 371 Args: |
| 370 pmap (arguments_pb2.PropertyMap): Map to convert to dictionary form. | 372 pmap (arguments_pb2.PropertyMap): Map to convert to dictionary form. |
| 371 Returns (dict): A dictionary derived from the properties in "pmap". | 373 Returns (dict): A dictionary derived from the properties in "pmap". |
| 372 """ | 374 """ |
| 373 return dict((k, _op_property_value(pmap[k])) for k in pmap) | 375 return dict((k, _op_property_value(pmap[k])) for k in pmap) |
| 374 | 376 |
| 375 | 377 |
| 376 def main(): | 378 def main(): |
| 377 from recipe_engine import package | |
| 378 | |
| 379 # Super-annoyingly, we need to manually parse for simulation_test since | 379 # Super-annoyingly, we need to manually parse for simulation_test since |
| 380 # argparse is bonkers and doesn't allow us to forward --help to subcommands. | 380 # argparse is bonkers and doesn't allow us to forward --help to subcommands. |
| 381 # Save old_args for if we're using bootstrap | 381 # Save old_args for if we're using bootstrap |
| 382 original_sys_argv = sys.argv[:] | 382 original_sys_argv = sys.argv[:] |
| 383 if 'simulation_test' in sys.argv: | 383 if 'simulation_test' in sys.argv: |
| 384 index = sys.argv.index('simulation_test') | 384 index = sys.argv.index('simulation_test') |
| 385 sys.argv = sys.argv[:index+1] + [json.dumps(sys.argv[index+1:])] | 385 sys.argv = sys.argv[:index+1] + [json.dumps(sys.argv[index+1:])] |
| 386 | 386 |
| 387 parser = argparse.ArgumentParser( | 387 parser = argparse.ArgumentParser( |
| 388 description='Interact with the recipe system.') | 388 description='Interact with the recipe system.') |
| 389 | 389 |
| 390 parser.add_argument( | 390 parser.add_argument( |
| 391 '--package', | 391 '--package', |
| 392 help='Path to recipes.cfg of the recipe package to operate on' | 392 help='Path to recipes.cfg of the recipe package to operate on' |
| 393 ', usually in infra/config/recipes.cfg') | 393 ', usually in infra/config/recipes.cfg') |
| 394 parser.add_argument( | 394 parser.add_argument( |
| 395 '--deps-path', | 395 '--deps-path', |
| 396 help='Path where recipe engine dependencies will be extracted.') | 396 help='Path where recipe engine dependencies will be extracted. Specify ' |
| 397 '"-" to use a temporary directory for deps, which will be cleaned ' | |
| 398 'up on exit.') | |
| 397 parser.add_argument( | 399 parser.add_argument( |
| 398 '--verbose', '-v', action='count', | 400 '--verbose', '-v', action='count', |
| 399 help='Increase logging verboisty') | 401 help='Increase logging verboisty') |
| 400 # TODO(phajdan.jr): Figure out if we need --no-fetch; remove if not. | 402 # TODO(phajdan.jr): Figure out if we need --no-fetch; remove if not. |
| 401 parser.add_argument( | 403 parser.add_argument( |
| 402 '--no-fetch', action='store_true', | 404 '--no-fetch', action='store_true', |
| 403 help='Disable automatic fetching') | 405 help='Disable automatic fetching') |
| 404 parser.add_argument('-O', '--project-override', metavar='ID=PATH', | 406 parser.add_argument('-O', '--project-override', metavar='ID=PATH', |
| 405 action=ProjectOverrideAction, | 407 action=ProjectOverrideAction, |
| 406 help='Override a project repository path with a local one.') | 408 help='Override a project repository path with a local one.') |
| (...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 554 | 556 |
| 555 args = parser.parse_args() | 557 args = parser.parse_args() |
| 556 | 558 |
| 557 # Load/parse operational arguments. | 559 # Load/parse operational arguments. |
| 558 op_args = arguments_pb2.Arguments() | 560 op_args = arguments_pb2.Arguments() |
| 559 if args.operational_args_path is not None: | 561 if args.operational_args_path is not None: |
| 560 with open(args.operational_args_path) as fd: | 562 with open(args.operational_args_path) as fd: |
| 561 data = fd.read() | 563 data = fd.read() |
| 562 jsonpb.Parse(data, op_args) | 564 jsonpb.Parse(data, op_args) |
| 563 | 565 |
| 564 if args.use_bootstrap and not os.environ.pop('RECIPES_RUN_BOOTSTRAP', None): | |
| 565 subprocess.check_call( | |
| 566 [sys.executable, 'bootstrap/bootstrap.py', '--deps-file', | |
| 567 'bootstrap/deps.pyl', 'ENV'], | |
| 568 cwd=os.path.dirname(os.path.realpath(__file__))) | |
| 569 | |
| 570 os.environ['RECIPES_RUN_BOOTSTRAP'] = '1' | |
| 571 args = sys.argv | |
| 572 return subprocess.call( | |
| 573 [os.path.join( | |
| 574 os.path.dirname(os.path.realpath(__file__)), 'ENV/bin/python'), | |
| 575 os.path.join(ROOT_DIR, 'recipes.py')] + original_sys_argv[1:]) | |
| 576 | |
| 577 if args.verbose > 1: | 566 if args.verbose > 1: |
| 578 logging.getLogger().setLevel(logging.DEBUG) | 567 logging.getLogger().setLevel(logging.DEBUG) |
| 579 elif args.verbose > 0: | 568 elif args.verbose > 0: |
| 580 logging.getLogger().setLevel(logging.INFO) | 569 logging.getLogger().setLevel(logging.INFO) |
| 581 | 570 |
| 571 # If we're using a temporary deps directory, create it. | |
| 572 temp_deps_dir = None | |
| 573 try: | |
| 574 # When bootstrapping, re-use the calling wrapper's deps directory instead of | |
| 575 # creating a new one. | |
| 576 args.deps_path = os.environ.pop('RECIPES_RUN_BOOTSTRAP_DEPS_DIR', | |
| 577 args.deps_path) | |
| 578 if args.deps_path == '-': | |
| 579 # "-" means use a temporary deps path. | |
| 580 temp_deps_dir = tempfile.mkdtemp(dir=ROOT_DIR, suffix='_deps') | |
| 581 args.deps_path = temp_deps_dir | |
| 582 | |
| 583 if args.deps_path: | |
| 584 logging.debug('Using custom deps path: %s', args.deps_path) | |
|
iannucci
2016/11/17 21:15:04
let's make this warning?
dnj
2016/11/17 21:27:56
Done.
Michael Moss
2016/12/02 17:08:57
Am I doing something wrong, or is expected that I
| |
| 585 | |
| 586 # If we're bootstrapping, construct our bootstrap environment. If we're | |
| 587 # using a custom deps path, install our enviornment there too. | |
| 588 if args.use_bootstrap and not os.environ.pop('RECIPES_RUN_BOOTSTRAP', None): | |
| 589 # Propagate our deps path, if specified, so we re-use our temporary | |
| 590 # directory. | |
| 591 if args.deps_path: | |
| 592 env_path = os.path.join(args.deps_path, 'ENV') | |
|
iannucci
2016/11/17 21:15:04
let's just make it .ENV
dnj
2016/11/17 21:27:56
Done.
| |
| 593 os.environ['RECIPES_RUN_BOOTSTRAP_DEPS_DIR'] = args.deps_path | |
| 594 else: | |
| 595 env_path = os.path.join(ROOT_DIR, 'ENV') | |
| 596 | |
| 597 logging.debug('Installing bootstrap environment into: %s', env_path) | |
| 598 subprocess.check_call( | |
| 599 [ | |
| 600 sys.executable, | |
| 601 os.path.join(ROOT_DIR, 'bootstrap', 'bootstrap.py'), | |
| 602 '--deps-file', os.path.join(ROOT_DIR, 'bootstrap', 'deps.pyl'), | |
| 603 env_path, | |
| 604 ], | |
| 605 cwd=ROOT_DIR) | |
| 606 | |
| 607 # Mark that we're bootstrapping, so the next invocation falls through to | |
| 608 # standard recipe operation. | |
| 609 os.environ['RECIPES_RUN_BOOTSTRAP'] = '1' | |
| 610 args = sys.argv | |
| 611 return subprocess.call( | |
| 612 [ | |
| 613 os.path.join(env_path, 'bin', 'python'), | |
| 614 os.path.join(ROOT_DIR, 'recipes.py'), | |
| 615 ] + original_sys_argv[1:]) | |
| 616 | |
| 617 # Standard recipe engine operation. | |
| 618 return _real_main(args, op_args) | |
| 619 | |
| 620 finally: | |
| 621 # If we're using a temporary deps directory, clean it up here. | |
| 622 if temp_deps_dir: | |
| 623 logging.info('Cleaning up temporary deps path: %s', temp_deps_dir) | |
| 624 shutil.rmtree(temp_deps_dir) | |
|
iannucci
2016/11/17 21:15:04
can we log the warning instead of throwing if this
dnj
2016/11/17 21:27:56
Done.
| |
| 625 | |
| 626 | |
| 627 def _real_main(args, op_args): | |
| 628 from recipe_engine import package | |
| 629 | |
| 582 # Commands which do not require config_file, package_deps, and other objects | 630 # Commands which do not require config_file, package_deps, and other objects |
| 583 # initialized later. | 631 # initialized later. |
| 584 if args.command == 'remote': | 632 if args.command == 'remote': |
| 585 return remote(args) | 633 return remote(args) |
| 586 | 634 |
| 587 repo_root, config_file = get_package_config(args) | 635 repo_root, config_file = get_package_config(args) |
| 588 | 636 |
| 589 try: | 637 try: |
| 590 # TODO(phajdan.jr): gracefully handle inconsistent deps when rolling. | 638 # TODO(phajdan.jr): gracefully handle inconsistent deps when rolling. |
| 591 # This fails if the starting point does not have consistent dependency | 639 # This fails if the starting point does not have consistent dependency |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 652 | 700 |
| 653 if not isinstance(ret, int): | 701 if not isinstance(ret, int): |
| 654 if ret is None: | 702 if ret is None: |
| 655 ret = 0 | 703 ret = 0 |
| 656 else: | 704 else: |
| 657 print >> sys.stderr, ret | 705 print >> sys.stderr, ret |
| 658 ret = 1 | 706 ret = 1 |
| 659 sys.stdout.flush() | 707 sys.stdout.flush() |
| 660 sys.stderr.flush() | 708 sys.stderr.flush() |
| 661 os._exit(ret) | 709 os._exit(ret) |
| OLD | NEW |