| 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..654347ef9caee984b91ff84b292e494022ea1669
|
| --- /dev/null
|
| +++ b/third_party/recipe_engine/recipes.py
|
| @@ -0,0 +1,223 @@
|
| +#!/usr/bin/env python
|
| +
|
| +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__))))
|
| +
|
| +
|
| +def get_package_pyl(args):
|
| + if args.package:
|
| + cand = os.path.realpath(os.path.join(os.getcwd(), args.package,
|
| + 'recipe_package.pyl'))
|
| + if os.path.exists(cand):
|
| + return cand
|
| + else:
|
| + raise EnvironmentError(
|
| + 'Specified package %s does not exist' % cand)
|
| +
|
| + cwd = os.path.realpath(os.getcwd())
|
| + while os.path.dirname(cwd) != cwd:
|
| + cand = os.path.join(cwd, 'recipe_package.pyl')
|
| + if os.path.exists(cand):
|
| + return cand
|
| +
|
| + 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.pyl' % cwd)
|
| + else:
|
| + raise EnvironmentError(
|
| + 'Reached root of filesystem when looking for recipe_package.pyl')
|
| +
|
| +def simulation_test(package, args):
|
| + from recipe_engine import simulation_test
|
| + simulation_test.main(package, args=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):
|
| + from recipe_engine import package
|
| + pyl_path = get_package_pyl(args)
|
| + context = package.PackageContext.from_pyl_path(pyl_path)
|
| + package_spec = package.PackageSpec.load_pyl(pyl_path)
|
| + package_spec_dump = package_spec.dump()
|
| +
|
| + for date, spec in package_spec.iterate_consistent_updates(context):
|
| + with open(pyl_path, 'w') as fh:
|
| + json.dump(spec.dump(), fh, indent=2, separators=(',', ': '))
|
| + print 'Wrote %s' % pyl_path
|
| +
|
| + updated_deps = {
|
| + dep_id: dep['revision']
|
| + for dep_id, dep in spec.dump()['deps'].iteritems()
|
| + if dep['revision'] != package_spec_dump['deps'][dep_id]['revision']
|
| + }
|
| + print 'To commit this roll, run:'
|
| + print ' '.join([
|
| + 'git commit -a --date "%s" -m "Roll dependencies"' % date.isoformat(),
|
| + ' '.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 main():
|
| + from recipe_engine import package
|
| +
|
| + parser = argparse.ArgumentParser(description='Do things with recipes.')
|
| +
|
| + parser.add_argument(
|
| + '--package',
|
| + help='Package to operate on (directory containing recipe_package.pyl)')
|
| + parser.add_argument(
|
| + '--verbose', '-v', action='store_true',
|
| + help='Increase logging verboisty')
|
| + parser.add_argument(
|
| + '--bootstrap', action='store_true',
|
| + help='Use current rather than pinned version of recipe engine')
|
| +
|
| + subp = parser.add_subparsers()
|
| +
|
| + 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', nargs=argparse.REMAINDER)
|
| +
|
| + 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')
|
| +
|
| + args = parser.parse_args()
|
| + if args.verbose:
|
| + logging.getLogger().setLevel(logging.INFO)
|
| +
|
| + package_deps = package.PackageDeps.create(get_package_pyl(args))
|
| + # 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').root_dir, 'recipes.py')
|
| + cmd = [ sys.executable, '-u', tool, '--bootstrap' ] + sys.argv[1:]
|
| + logging.info(cmd)
|
| + return subprocess.call(cmd)
|
| +
|
| + 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)
|
| + 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())
|
|
|