| 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 16 matching lines...) Expand all Loading... |
| 27 sys.path.insert(0, ROOT_DIR) | 27 sys.path.insert(0, ROOT_DIR) |
| 28 | 28 |
| 29 from recipe_engine import env | 29 from recipe_engine import env |
| 30 | 30 |
| 31 import argparse # this is vendored | 31 import argparse # this is vendored |
| 32 from recipe_engine import arguments_pb2 | 32 from recipe_engine import arguments_pb2 |
| 33 from recipe_engine import util as recipe_util | 33 from recipe_engine import util as recipe_util |
| 34 from google.protobuf import json_format as jsonpb | 34 from google.protobuf import json_format as jsonpb |
| 35 | 35 |
| 36 | 36 |
| 37 def test(config_file, package_deps, args): | |
| 38 try: | |
| 39 from recipe_engine import test | |
| 40 except ImportError: | |
| 41 logging.error( | |
| 42 'Error while importing testing libraries. You may be missing the pip' | |
| 43 ' package "coverage". Install it, or use the --use-bootstrap command' | |
| 44 ' line argument when calling into the recipe engine, which will install' | |
| 45 ' it for you.') | |
| 46 raise | |
| 47 | |
| 48 from recipe_engine import loader | |
| 49 from recipe_engine import package | |
| 50 | |
| 51 universe = loader.RecipeUniverse(package_deps, config_file) | |
| 52 universe_view = loader.UniverseView(universe, package_deps.root_package) | |
| 53 | |
| 54 # Prevent flakiness caused by stale pyc files. | |
| 55 package.cleanup_pyc(package_deps.root_package.recipes_dir) | |
| 56 | |
| 57 return test.main( | |
| 58 universe_view, raw_args=args.args, | |
| 59 engine_flags=args.operational_args.engine_flags) | |
| 60 | |
| 61 | |
| 62 def handle_recipe_return(recipe_result, result_filename, stream_engine, | 37 def handle_recipe_return(recipe_result, result_filename, stream_engine, |
| 63 engine_flags): | 38 engine_flags): |
| 64 if engine_flags and engine_flags.use_result_proto: | 39 if engine_flags and engine_flags.use_result_proto: |
| 65 return new_handle_recipe_return( | 40 return new_handle_recipe_return( |
| 66 recipe_result, result_filename, stream_engine) | 41 recipe_result, result_filename, stream_engine) |
| 67 | 42 |
| 68 if 'recipe_result' in recipe_result.result: | 43 if 'recipe_result' in recipe_result.result: |
| 69 result_string = json.dumps( | 44 result_string = json.dumps( |
| 70 recipe_result.result['recipe_result'], indent=2) | 45 recipe_result.result['recipe_result'], indent=2) |
| 71 if result_filename: | 46 if result_filename: |
| (...skipping 254 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 326 '--disable-bootstrap', action='store_false', dest='use_bootstrap', | 301 '--disable-bootstrap', action='store_false', dest='use_bootstrap', |
| 327 help='Disables bootstrap (see --use-bootstrap)') | 302 help='Disables bootstrap (see --use-bootstrap)') |
| 328 | 303 |
| 329 def operational_args_type(value): | 304 def operational_args_type(value): |
| 330 with open(value) as fd: | 305 with open(value) as fd: |
| 331 return jsonpb.ParseDict(json.load(fd), arguments_pb2.Arguments()) | 306 return jsonpb.ParseDict(json.load(fd), arguments_pb2.Arguments()) |
| 332 | 307 |
| 333 parser.set_defaults( | 308 parser.set_defaults( |
| 334 operational_args=arguments_pb2.Arguments(), | 309 operational_args=arguments_pb2.Arguments(), |
| 335 bare_command=False, # don't call postprocess_func, don't use package_deps | 310 bare_command=False, # don't call postprocess_func, don't use package_deps |
| 311 postprocess_func=lambda parser, args: None, |
| 336 ) | 312 ) |
| 337 | 313 |
| 338 parser.add_argument( | 314 parser.add_argument( |
| 339 '--operational-args-path', | 315 '--operational-args-path', |
| 340 dest='operational_args', | 316 dest='operational_args', |
| 341 type=operational_args_type, | 317 type=operational_args_type, |
| 342 help='The path to an operational Arguments file. If provided, this file ' | 318 help='The path to an operational Arguments file. If provided, this file ' |
| 343 'must contain a JSONPB-encoded Arguments protobuf message, and will ' | 319 'must contain a JSONPB-encoded Arguments protobuf message, and will ' |
| 344 'be integrated into the runtime parameters.') | 320 'be integrated into the runtime parameters.') |
| 345 | 321 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 358 return post_process_args | 334 return post_process_args |
| 359 | 335 |
| 360 | 336 |
| 361 def main(): | 337 def main(): |
| 362 parser = argparse.ArgumentParser( | 338 parser = argparse.ArgumentParser( |
| 363 description='Interact with the recipe system.') | 339 description='Interact with the recipe system.') |
| 364 | 340 |
| 365 common_postprocess_func = add_common_args(parser) | 341 common_postprocess_func = add_common_args(parser) |
| 366 | 342 |
| 367 from recipe_engine import fetch, lint_test, bundle, depgraph, autoroll | 343 from recipe_engine import fetch, lint_test, bundle, depgraph, autoroll |
| 368 from recipe_engine import remote, refs, doc | 344 from recipe_engine import remote, refs, doc, test |
| 369 to_add = [fetch, lint_test, bundle, depgraph, autoroll, remote, refs, doc] | 345 to_add = [ |
| 346 fetch, lint_test, bundle, depgraph, autoroll, remote, refs, doc, test, |
| 347 ] |
| 370 | 348 |
| 371 subp = parser.add_subparsers() | 349 subp = parser.add_subparsers() |
| 372 for module in to_add: | 350 for module in to_add: |
| 373 module.add_subparser(subp) | 351 module.add_subparser(subp) |
| 374 | 352 |
| 375 test_p = subp.add_parser( | |
| 376 'test', | |
| 377 description='Generate or check expectations by simulation') | |
| 378 test_p.set_defaults(command='test') | |
| 379 test_p.add_argument('args', nargs=argparse.REMAINDER) | |
| 380 | |
| 381 | 353 |
| 382 def properties_file_type(filename): | 354 def properties_file_type(filename): |
| 383 with (sys.stdin if filename == '-' else open(filename)) as f: | 355 with (sys.stdin if filename == '-' else open(filename)) as f: |
| 384 obj = json.load(f) | 356 obj = json.load(f) |
| 385 if not isinstance(obj, dict): | 357 if not isinstance(obj, dict): |
| 386 raise argparse.ArgumentTypeError( | 358 raise argparse.ArgumentTypeError( |
| 387 'must contain a JSON object, i.e. `{}`.') | 359 'must contain a JSON object, i.e. `{}`.') |
| 388 return obj | 360 return obj |
| 389 | 361 |
| 390 def parse_prop(prop): | 362 def parse_prop(prop): |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 444 run_p.add_argument( | 416 run_p.add_argument( |
| 445 'props', | 417 'props', |
| 446 nargs=argparse.REMAINDER, | 418 nargs=argparse.REMAINDER, |
| 447 type=parse_prop, | 419 type=parse_prop, |
| 448 help='A list of property pairs; e.g. mastername=chromium.linux ' | 420 help='A list of property pairs; e.g. mastername=chromium.linux ' |
| 449 'issue=12345. The property value will be decoded as JSON, but if ' | 421 'issue=12345. The property value will be decoded as JSON, but if ' |
| 450 'this decoding fails the value will be interpreted as a string.') | 422 'this decoding fails the value will be interpreted as a string.') |
| 451 | 423 |
| 452 args = parser.parse_args() | 424 args = parser.parse_args() |
| 453 common_postprocess_func(parser, args) | 425 common_postprocess_func(parser, args) |
| 454 if hasattr(args, 'postprocess_func'): | 426 args.postprocess_func(parser, args) |
| 455 args.postprocess_func(parser, args) | |
| 456 | 427 |
| 457 # TODO(iannucci): We should always do logging.basicConfig() (probably with | 428 # TODO(iannucci): We should always do logging.basicConfig() (probably with |
| 458 # logging.WARNING), even if no verbose is passed. However we need to be | 429 # logging.WARNING), even if no verbose is passed. However we need to be |
| 459 # careful as this could cause issues with spurious/unexpected output. I think | 430 # careful as this could cause issues with spurious/unexpected output. I think |
| 460 # it's risky enough to do in a different CL. | 431 # it's risky enough to do in a different CL. |
| 461 | 432 |
| 462 if args.verbose > 0: | 433 if args.verbose > 0: |
| 463 logging.basicConfig() | 434 logging.basicConfig() |
| 464 logging.getLogger().setLevel(logging.INFO) | 435 logging.getLogger().setLevel(logging.INFO) |
| 465 if args.verbose > 1: | 436 if args.verbose > 1: |
| 466 logging.getLogger().setLevel(logging.DEBUG) | 437 logging.getLogger().setLevel(logging.DEBUG) |
| 467 | 438 |
| 468 # Auto-enable bootstrap for test command invocations (necessary to get recent | |
| 469 # enough version of coverage package), unless explicitly disabled. | |
| 470 if args.command == 'test' and args.use_bootstrap is None: | |
| 471 args.use_bootstrap = True | |
| 472 | |
| 473 # If we're bootstrapping, construct our bootstrap environment. If we're | 439 # If we're bootstrapping, construct our bootstrap environment. If we're |
| 474 # using a custom deps path, install our enviornment there too. | 440 # using a custom deps path, install our enviornment there too. |
| 475 if args.use_bootstrap and not env.USING_BOOTSTRAP: | 441 if args.use_bootstrap and not env.USING_BOOTSTRAP: |
| 476 logging.debug('Bootstrapping recipe engine through vpython...') | 442 logging.debug('Bootstrapping recipe engine through vpython...') |
| 477 | 443 |
| 478 bootstrap_env = os.environ.copy() | 444 bootstrap_env = os.environ.copy() |
| 479 bootstrap_env[env.BOOTSTRAP_ENV_KEY] = '1' | 445 bootstrap_env[env.BOOTSTRAP_ENV_KEY] = '1' |
| 480 | 446 |
| 481 cmd = [ | 447 cmd = [ |
| 482 sys.executable, | 448 sys.executable, |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 518 repo_root, config_file, allow_fetch=not args.no_fetch, | 484 repo_root, config_file, allow_fetch=not args.no_fetch, |
| 519 deps_path=args.deps_path, overrides=args.project_override) | 485 deps_path=args.deps_path, overrides=args.project_override) |
| 520 except subprocess.CalledProcessError: | 486 except subprocess.CalledProcessError: |
| 521 # A git checkout failed somewhere. Return 2, which is the sign that this is | 487 # A git checkout failed somewhere. Return 2, which is the sign that this is |
| 522 # an infra failure, rather than a test failure. | 488 # an infra failure, rather than a test failure. |
| 523 return 2 | 489 return 2 |
| 524 | 490 |
| 525 if hasattr(args, 'func'): | 491 if hasattr(args, 'func'): |
| 526 return args.func(package_deps, args) | 492 return args.func(package_deps, args) |
| 527 | 493 |
| 528 if args.command == 'test': | 494 if args.command == 'run': |
| 529 return test(config_file, package_deps, args) | |
| 530 elif args.command == 'run': | |
| 531 return run(config_file, package_deps, args) | 495 return run(config_file, package_deps, args) |
| 532 else: | 496 else: |
| 533 print """Dear sir or madam, | 497 print """Dear sir or madam, |
| 534 It has come to my attention that a quite impossible condition has come | 498 It has come to my attention that a quite impossible condition has come |
| 535 to pass in the specification you have issued a request for us to fulfill. | 499 to pass in the specification you have issued a request for us to fulfill. |
| 536 It is with a heavy heart that I inform you that, at the present juncture, | 500 It is with a heavy heart that I inform you that, at the present juncture, |
| 537 there is no conceivable next action to be taken upon your request, and as | 501 there is no conceivable next action to be taken upon your request, and as |
| 538 such, we have decided to abort the request with a nonzero status code. We | 502 such, we have decided to abort the request with a nonzero status code. We |
| 539 hope that your larger goals have not been put at risk due to this | 503 hope that your larger goals have not been put at risk due to this |
| 540 unfortunate circumstance, and wish you the best in deciding the next action | 504 unfortunate circumstance, and wish you the best in deciding the next action |
| (...skipping 20 matching lines...) Expand all Loading... |
| 561 | 525 |
| 562 if not isinstance(ret, int): | 526 if not isinstance(ret, int): |
| 563 if ret is None: | 527 if ret is None: |
| 564 ret = 0 | 528 ret = 0 |
| 565 else: | 529 else: |
| 566 print >> sys.stderr, ret | 530 print >> sys.stderr, ret |
| 567 ret = 1 | 531 ret = 1 |
| 568 sys.stdout.flush() | 532 sys.stdout.flush() |
| 569 sys.stderr.flush() | 533 sys.stderr.flush() |
| 570 os._exit(ret) | 534 os._exit(ret) |
| OLD | NEW |