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 |