Chromium Code Reviews| Index: third_party/recipe_engine/recipes.py |
| diff --git a/third_party/recipe_engine/recipes.py b/third_party/recipe_engine/recipes.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..aa71a98f480da770e54f8277cc48ee6acab0f96c |
| --- /dev/null |
| +++ b/third_party/recipe_engine/recipes.py |
| @@ -0,0 +1,265 @@ |
| +#!/usr/bin/env python |
|
iannucci
2015/08/06 23:57:13
maybe call this main.py and rename main.py to run.
luqui
2015/08/20 22:45:25
Done.
|
| + |
| +"""Tool to interact with recipe repositories. |
| + |
| +This tool operates on the nearest ancestor directory containing a |
| +recipe_package.pyl file. |
|
luqui
2015/08/07 19:30:36
Update docstring
luqui
2015/08/20 22:45:24
Done.
|
| +""" |
| + |
| +import argparse |
| +import json |
| +import logging |
| +import os |
| +import subprocess |
| +import sys |
| + |
| +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
iannucci
2015/08/06 23:57:13
!!!!!!!
luqui
2015/08/20 22:45:25
!!!!!!!
|
| + |
| + |
| +def get_package_config(args): |
| + from recipe_engine import package |
| + |
| + def try_path(path): |
| + cand = os.path.realpath( |
| + os.path.join(os.getcwd(), path, 'infra', 'config', 'recipes.cfg')) |
| + if os.path.exists(cand): |
| + return package.ProtoFile(cand) |
| + else: |
| + raise EnvironmentError( |
| + 'Recipes config file %s does not exist' % cand) |
| + |
| + if args.package: |
| + return try_path(args.package) |
| + |
| + cwd = os.path.realpath(os.getcwd()) |
| + while os.path.dirname(cwd) != cwd: |
| + try: |
| + return try_path(cwd) |
| + except EnvironmentError: |
| + if not os.path.isdir(os.path.join(cwd, '.git')): |
| + cwd = os.path.dirname(cwd) |
| + else: |
| + raise EnvironmentError( |
| + 'Will not cross git repository boundary %s when looking for ' |
| + 'recipe package spec' % cwd) |
| + else: |
| + raise EnvironmentError( |
| + 'Reached root of filesystem when looking for recipe package spec') |
| + |
| + |
| +def simulation_test(package, args): |
| + from recipe_engine import simulation_test |
| + simulation_test.main(package, args=json.loads(args.args)) |
| + |
| + |
| +def lint(package, args): |
| + from recipe_engine import lint_test |
| + lint_test.main(package) |
| + |
| + |
| +def run(package, args): |
| + from recipe_engine import main as recipe_main |
| + from recipe_engine import loader |
| + from recipe_engine import package |
| + from recipe_engine.third_party import annotator |
| + |
| + def get_properties_from_args(args): |
| + properties = dict(x.split('=', 1) for x in args) |
| + for key, val in properties.iteritems(): |
| + try: |
| + properties[key] = ast.literal_eval(val) |
| + except (ValueError, SyntaxError): |
| + pass # If a value couldn't be evaluated, keep the string version |
| + return properties |
| + |
| + def get_properties_from_file(filename): |
| + properties_file = sys.stdin if filename == '-' else open(filename) |
| + properties = ast.literal_eval(properties_file.read()) |
| + assert isinstance(properties, dict) |
| + |
| + arg_properties = get_properties_from_args(args.properties) |
| + if args.properties_file and arg_properties: |
| + raise Exception( |
| + 'Cannot specify both properties file and command-line properties') |
| + elif args.properties_file: |
| + properties = get_properties_from_file(args.properties_file) |
| + else: |
| + properties = arg_properties |
| + |
| + properties['recipe'] = args.recipe |
| + |
| + # TODO(luqui): Environment whitelist. Cf crbug.com/392992 |
| + os.environ['PYTHONUNBUFFERED'] = '1' |
| + os.environ['PYTHONIOENCODING'] = 'UTF-8' |
| + |
| + universe = loader.RecipeUniverse(package) |
| + |
| + workdir = (args.workdir or |
| + os.path.join(os.path.dirname(os.path.realpath(__file__)), 'workdir')) |
| + logging.info('Using %s as work directory' % workdir) |
| + if not os.path.exists(workdir): |
| + os.makedirs(workdir) |
| + |
| + old_cwd = os.getcwd() |
| + os.chdir(workdir) |
| + try: |
| + ret = recipe_main.run_steps( |
| + properties, annotator.StructuredAnnotationStream(), universe=universe) |
| + return ret.status_code |
| + finally: |
| + os.chdir(old_cwd) |
| + |
| + |
| +def roll(args): |
|
iannucci
2015/08/06 23:57:13
maybe have option to do this in a loop until it ca
luqui
2015/08/20 22:45:25
On second thought, no. The reason for one at a ti
|
| + from recipe_engine import package |
| + config_file = get_package_config(args) |
| + context = package.PackageContext.from_proto_file(config_file) |
| + package_spec = package.PackageSpec.load_proto(config_file) |
| + |
| + for update in package_spec.iterate_consistent_updates(context): |
| + config_file.write(update.spec.dump()) |
| + print 'Wrote %s' % config_file.path |
| + |
| + updated_deps = { |
| + dep_id: dep.revision |
| + for dep_id, dep in update.spec.deps.iteritems() |
| + if dep.revision != package_spec.deps[dep_id].revision |
| + } |
| + print 'To commit this roll, run:' |
| + print ' '.join([ |
| + 'git commit -a -m "Roll dependencies"', |
| + ' '.join([ '-m "Roll %s to %s"' % (dep_id, rev) |
| + for dep_id, rev in sorted(updated_deps.iteritems())]), |
| + ]) |
| + |
| + break |
| + else: |
| + print 'No consistent rolls found' |
| + |
| + |
| +def show_me_the_modules(package): |
| + from recipe_engine import show_me_the_modules |
| + show_me_the_modules.main(package) |
| + |
| + |
| +def main(): |
| + from recipe_engine import package |
| + |
| + # Super-annoyingly, we need to manually parse for simulation_test since |
| + # argparse is bonkers and doesn't allow us to forward --help to subcommands. |
| + if 'simulation_test' in sys.argv: |
| + index = sys.argv.index('simulation_test') |
| + sys.argv = sys.argv[:index+1] + [json.dumps(sys.argv[index+1:])] |
| + |
| + parser = argparse.ArgumentParser(description='Do things with recipes.') |
| + |
| + parser.add_argument( |
| + '--package', |
| + help='Package to operate on (directory containing ' |
| + 'infra/config/recipes.cfg)') |
| + parser.add_argument( |
| + '--verbose', '-v', action='store_true', |
| + help='Increase logging verboisty') |
| + parser.add_argument( |
| + '--bootstrap', action='store_true', |
|
luqui
2015/08/07 19:30:36
--no-fetch
Don't checkout recipe engine at HEAD.
luqui
2015/08/20 22:45:24
Done.
|
| + help='Use current rather than pinned version of recipe engine') |
| + |
| + subp = parser.add_subparsers() |
| + |
| + fetch_p = subp.add_parser( |
| + 'fetch', |
| + help='Fetch and update dependencies.') |
| + fetch_p.set_defaults(command='fetch') |
| + |
| + simulation_test_p = subp.add_parser('simulation_test', |
| + help='Generate or check expectations by simulating with mock actions') |
| + simulation_test_p.set_defaults(command='simulation_test') |
| + simulation_test_p.add_argument('args') |
| + |
| + lint_p = subp.add_parser( |
| + 'lint', |
| + help='Check recipes for stylistic and hygenic issues') |
| + lint_p.set_defaults(command='lint') |
| + |
| + run_p = subp.add_parser( |
| + 'run', |
| + help='Run a recipe locally') |
| + run_p.set_defaults(command='run') |
| + run_p.add_argument( |
| + '--properties-file', |
| + help='A file containing a json blob of properties') |
| + run_p.add_argument( |
| + '--workdir', |
| + help='The working directory of recipe execution') |
| + run_p.add_argument( |
| + 'recipe', |
| + help='The recipe to execute') |
| + run_p.add_argument( |
| + 'properties', nargs=argparse.REMAINDER, |
| + help='A list of property pairs; e.g. mastername=chromium.linux ' |
| + 'issue=12345') |
| + |
| + roll_p = subp.add_parser( |
| + 'roll', |
| + help='Roll dependencies of a recipe package forward') |
| + roll_p.set_defaults(command='roll') |
| + |
| + show_me_the_modules_p = subp.add_parser( |
| + 'show_me_the_modules', |
|
iannucci
2015/08/06 23:57:13
maybe 'doc'?
doc <module name>
doc <recipe name>
luqui
2015/08/20 22:45:25
Done
|
| + help='List all known modules reachable from the current package with ' |
| + 'various info about each') |
| + show_me_the_modules_p.set_defaults(command='show_me_the_modules') |
| + |
| + args = parser.parse_args() |
| + if args.verbose: |
| + logging.getLogger().setLevel(logging.INFO) |
| + |
| + if args.command == 'fetch' or args.command == 'roll': |
| + package_deps = package.PackageDeps.create( |
| + get_package_config(args), fetch=True) |
| + else: |
| + package_deps = package.PackageDeps.create(get_package_config(args)) |
|
iannucci
2015/08/06 23:57:13
let's have a `fetch = <condition>` line, and then
luqui
2015/08/20 22:45:24
Nope, because of error message stuff.
|
| + |
| + # We don't forward to pinned tool on rolling, because there might be new |
| + # things to know about since then, who knows? Easier to look backward than |
| + # forward. |
| + if not args.bootstrap and args.command != 'roll': |
| + tool = os.path.join( |
| + package_deps.get_package('recipe_engine').recipes_dir, 'recipes.py') |
| + cmd = [ sys.executable, '-u', tool, '--bootstrap' ] + sys.argv[1:] |
| + logging.info(cmd) |
| + return subprocess.call(cmd) |
| + |
| + if args.command == 'fetch': |
| + # We already did everything in the create() call above. |
| + return 0 |
| + if args.command == 'simulation_test': |
| + return simulation_test(package_deps, args) |
| + elif args.command == 'lint': |
| + return lint(package_deps, args) |
| + elif args.command == 'run': |
| + return run(package_deps, args) |
| + elif args.command == 'roll': |
| + return roll(args) |
| + elif args.command == 'show_me_the_modules': |
| + return show_me_the_modules(package_deps) |
| + else: |
| + print """Dear sir or madam, |
| + It has come to my attention that a quite impossible condition has come |
| + to pass in the specification you have issued a request for us to fulfill. |
| + It is with a heavy heart that I inform you that, at the present juncture, |
| + there is no conceivable next action to be taken upon your request, and as |
| + such, we have decided to abort the request with a nonzero status code. We |
| + hope that your larger goals have not been put at risk due to this |
| + unfortunate circumstance, and wish you the best in deciding the next action |
| + in your venture and larger life. |
| + |
| + Warmly, |
| + recipes.py |
| + """ |
| + return 1 |
| + |
| + return 0 |
| + |
| +if __name__ == '__main__': |
| + sys.exit(main()) |