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 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
213 step_runner.SubprocessStepRunner(stream_engine, engine_flags), | 213 step_runner.SubprocessStepRunner(stream_engine, engine_flags), |
214 universe_view, engine_flags=engine_flags, | 214 universe_view, engine_flags=engine_flags, |
215 emit_initial_properties=emit_initial_properties) | 215 emit_initial_properties=emit_initial_properties) |
216 finally: | 216 finally: |
217 os.chdir(old_cwd) | 217 os.chdir(old_cwd) |
218 | 218 |
219 return handle_recipe_return( | 219 return handle_recipe_return( |
220 ret, args.output_result_json, stream_engine, engine_flags) | 220 ret, args.output_result_json, stream_engine, engine_flags) |
221 | 221 |
222 | 222 |
223 def remote(args): | |
224 from recipe_engine import remote | |
225 | |
226 return remote.main(args) | |
227 | |
228 | |
229 class ProjectOverrideAction(argparse.Action): | 223 class ProjectOverrideAction(argparse.Action): |
230 def __call__(self, parser, namespace, values, option_string=None): | 224 def __call__(self, parser, namespace, values, option_string=None): |
231 p = values.split('=', 2) | 225 p = values.split('=', 2) |
232 if len(p) != 2: | 226 if len(p) != 2: |
233 raise ValueError('Override must have the form: repo=path') | 227 raise ValueError('Override must have the form: repo=path') |
234 project_id, path = p | 228 project_id, path = p |
235 | 229 |
236 v = getattr(namespace, self.dest, None) | 230 v = getattr(namespace, self.dest, None) |
237 if v is None: | 231 if v is None: |
238 v = {} | 232 v = {} |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
344 help='Use bootstrap/bootstrap.py to create a isolated python virtualenv' | 338 help='Use bootstrap/bootstrap.py to create a isolated python virtualenv' |
345 ' with required python dependencies.') | 339 ' with required python dependencies.') |
346 parser.add_argument( | 340 parser.add_argument( |
347 '--disable-bootstrap', action='store_false', dest='use_bootstrap', | 341 '--disable-bootstrap', action='store_false', dest='use_bootstrap', |
348 help='Disables bootstrap (see --use-bootstrap)') | 342 help='Disables bootstrap (see --use-bootstrap)') |
349 | 343 |
350 def operational_args_type(value): | 344 def operational_args_type(value): |
351 with open(value) as fd: | 345 with open(value) as fd: |
352 return jsonpb.ParseDict(json.load(fd), arguments_pb2.Arguments()) | 346 return jsonpb.ParseDict(json.load(fd), arguments_pb2.Arguments()) |
353 | 347 |
354 parser.set_defaults(operational_args=arguments_pb2.Arguments()) | 348 parser.set_defaults( |
349 operational_args=arguments_pb2.Arguments(), | |
350 bare_command=False, # don't call postprocess_func, don't use package_deps | |
dnj
2017/04/27 16:38:40
add a TODO here, remove this when "remote" command
| |
351 ) | |
355 | 352 |
356 parser.add_argument( | 353 parser.add_argument( |
357 '--operational-args-path', | 354 '--operational-args-path', |
358 dest='operational_args', | 355 dest='operational_args', |
359 type=operational_args_type, | 356 type=operational_args_type, |
360 help='The path to an operational Arguments file. If provided, this file ' | 357 help='The path to an operational Arguments file. If provided, this file ' |
361 'must contain a JSONPB-encoded Arguments protobuf message, and will ' | 358 'must contain a JSONPB-encoded Arguments protobuf message, and will ' |
362 'be integrated into the runtime parameters.') | 359 'be integrated into the runtime parameters.') |
363 | 360 |
364 def post_process_args(parser, args): | 361 def post_process_args(parser, args): |
365 if args.command == 'remote': | 362 if args.bare_command: |
366 # TODO(iannucci): this is a hack; remote doesn't behave like ANY other | 363 # TODO(iannucci): this is gross, and only for the remote subcommand; |
367 # commands. A way to solve this will be to allow --package to take | 364 # remote doesn't behave like ANY other commands. A way to solve this will |
368 # a remote repo and then simply remove the remote subcommand entirely. | 365 # be to allow --package to take a remote repo and then simply remove the |
369 return | 366 # remote subcommand entirely. |
370 | 367 if args.package is not None: |
371 if not args.package: | 368 parser.error('%s forbids --package' % args.command) |
372 parser.error('%s requires --package' % args.command) | 369 else: |
370 if not args.package: | |
371 parser.error('%s requires --package' % args.command) | |
373 | 372 |
374 return post_process_args | 373 return post_process_args |
375 | 374 |
376 | 375 |
377 def main(): | 376 def main(): |
378 parser = argparse.ArgumentParser( | 377 parser = argparse.ArgumentParser( |
379 description='Interact with the recipe system.') | 378 description='Interact with the recipe system.') |
380 | 379 |
381 common_postprocess_func = add_common_args(parser) | 380 common_postprocess_func = add_common_args(parser) |
382 | 381 |
383 from recipe_engine import fetch, lint_test, bundle, depgraph, autoroll | 382 from recipe_engine import fetch, lint_test, bundle, depgraph, autoroll |
384 to_add = [fetch, lint_test, bundle, depgraph, autoroll] | 383 from recipe_engine import remote |
dnj
2017/04/27 16:38:40
Since you're adding a lot of optional flags, can w
| |
384 to_add = [fetch, lint_test, bundle, depgraph, autoroll, remote] | |
385 | 385 |
386 subp = parser.add_subparsers() | 386 subp = parser.add_subparsers() |
387 for module in to_add: | 387 for module in to_add: |
388 module.add_subparser(subp) | 388 module.add_subparser(subp) |
389 | 389 |
390 test_p = subp.add_parser( | 390 test_p = subp.add_parser( |
391 'test', | 391 'test', |
392 description='Generate or check expectations by simulation') | 392 description='Generate or check expectations by simulation') |
393 test_p.set_defaults(command='test') | 393 test_p.set_defaults(command='test') |
394 test_p.add_argument('args', nargs=argparse.REMAINDER) | 394 test_p.add_argument('args', nargs=argparse.REMAINDER) |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
457 'recipe', | 457 'recipe', |
458 help='The recipe to execute') | 458 help='The recipe to execute') |
459 run_p.add_argument( | 459 run_p.add_argument( |
460 'props', | 460 'props', |
461 nargs=argparse.REMAINDER, | 461 nargs=argparse.REMAINDER, |
462 type=parse_prop, | 462 type=parse_prop, |
463 help='A list of property pairs; e.g. mastername=chromium.linux ' | 463 help='A list of property pairs; e.g. mastername=chromium.linux ' |
464 'issue=12345. The property value will be decoded as JSON, but if ' | 464 'issue=12345. The property value will be decoded as JSON, but if ' |
465 'this decoding fails the value will be interpreted as a string.') | 465 'this decoding fails the value will be interpreted as a string.') |
466 | 466 |
467 | |
468 remote_p = subp.add_parser( | |
469 'remote', | |
470 description='Invoke a recipe command from specified repo and revision') | |
471 remote_p.set_defaults(command='remote') | |
472 remote_p.add_argument( | |
473 '--repository', required=True, | |
474 help='URL of a git repository to fetch') | |
475 remote_p.add_argument( | |
476 '--revision', | |
477 help=( | |
478 'Git commit hash to check out; defaults to latest revision on master' | |
479 ' (refs/heads/master)' | |
480 )) | |
481 remote_p.add_argument( | |
482 '--workdir', | |
483 type=os.path.abspath, | |
484 help='The working directory of repo checkout') | |
485 remote_p.add_argument( | |
486 '--use-gitiles', action='store_true', | |
487 help='Use Gitiles-specific way to fetch repo (potentially cheaper for ' | |
488 'large repos)') | |
489 remote_p.add_argument( | |
490 'remote_args', nargs='*', | |
491 help='Arguments to pass to fetched repo\'s recipes.py') | |
492 | |
493 | |
494 refs_p = subp.add_parser( | 467 refs_p = subp.add_parser( |
495 'refs', | 468 'refs', |
496 description='List places referencing given recipe module(s).') | 469 description='List places referencing given recipe module(s).') |
497 refs_p.set_defaults(command='refs') | 470 refs_p.set_defaults(command='refs') |
498 refs_p.add_argument('modules', nargs='+', help='Module(s) to query for') | 471 refs_p.add_argument('modules', nargs='+', help='Module(s) to query for') |
499 refs_p.add_argument('--transitive', action='store_true', | 472 refs_p.add_argument('--transitive', action='store_true', |
500 help='Compute transitive closure of the references') | 473 help='Compute transitive closure of the references') |
501 | 474 |
502 doc_kinds=('binarypb', 'jsonpb', 'textpb', 'markdown(github)', | 475 doc_kinds=('binarypb', 'jsonpb', 'textpb', 'markdown(github)', |
503 'markdown(gitiles)') | 476 'markdown(gitiles)') |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
599 # wrong, log the error, but don't actually raise anything. | 572 # wrong, log the error, but don't actually raise anything. |
600 def on_error(_function, path, excinfo): | 573 def on_error(_function, path, excinfo): |
601 logging.error('Error cleaning up temporary deps file: %s', path, | 574 logging.error('Error cleaning up temporary deps file: %s', path, |
602 exc_info=excinfo) | 575 exc_info=excinfo) |
603 shutil.rmtree(temp_deps_dir, onerror=on_error) | 576 shutil.rmtree(temp_deps_dir, onerror=on_error) |
604 | 577 |
605 | 578 |
606 def _real_main(args): | 579 def _real_main(args): |
607 from recipe_engine import package | 580 from recipe_engine import package |
608 | 581 |
609 # Commands which do not require config_file, package_deps, and other objects | 582 if args.bare_command: |
610 # initialized later. | 583 return args.func(None, args) |
611 if args.command == 'remote': | |
612 return remote(args) | |
613 | 584 |
614 config_file = args.package | 585 config_file = args.package |
615 repo_root = package.InfraRepoConfig().from_recipes_cfg(args.package.path) | 586 repo_root = package.InfraRepoConfig().from_recipes_cfg(args.package.path) |
616 | 587 |
617 try: | 588 try: |
618 # TODO(phajdan.jr): gracefully handle inconsistent deps when rolling. | 589 # TODO(phajdan.jr): gracefully handle inconsistent deps when rolling. |
619 # This fails if the starting point does not have consistent dependency | 590 # This fails if the starting point does not have consistent dependency |
620 # graph. When performing an automated roll, it'd make sense to attempt | 591 # graph. When performing an automated roll, it'd make sense to attempt |
621 # to automatically find a consistent state, rather than bailing out. | 592 # to automatically find a consistent state, rather than bailing out. |
622 # Especially that only some subcommands refer to package_deps. | 593 # Especially that only some subcommands refer to package_deps. |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
671 | 642 |
672 if not isinstance(ret, int): | 643 if not isinstance(ret, int): |
673 if ret is None: | 644 if ret is None: |
674 ret = 0 | 645 ret = 0 |
675 else: | 646 else: |
676 print >> sys.stderr, ret | 647 print >> sys.stderr, ret |
677 ret = 1 | 648 ret = 1 |
678 sys.stdout.flush() | 649 sys.stdout.flush() |
679 sys.stderr.flush() | 650 sys.stderr.flush() |
680 os._exit(ret) | 651 os._exit(ret) |
OLD | NEW |