OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 |
| 3 import argparse |
| 4 import json |
| 5 import logging |
| 6 import os |
| 7 import subprocess |
| 8 import sys |
| 9 |
| 10 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
| 11 |
| 12 |
| 13 def get_package_pyl(args): |
| 14 if args.package: |
| 15 cand = os.path.realpath(os.path.join(os.getcwd(), args.package, |
| 16 'recipe_package.pyl')) |
| 17 if os.path.exists(cand): |
| 18 return cand |
| 19 else: |
| 20 raise EnvironmentError( |
| 21 'Specified package %s does not exist' % cand) |
| 22 |
| 23 cwd = os.path.realpath(os.getcwd()) |
| 24 while os.path.dirname(cwd) != cwd: |
| 25 cand = os.path.join(cwd, 'recipe_package.pyl') |
| 26 if os.path.exists(cand): |
| 27 return cand |
| 28 |
| 29 if not os.path.isdir(os.path.join(cwd, '.git')): |
| 30 cwd = os.path.dirname(cwd) |
| 31 else: |
| 32 raise EnvironmentError( |
| 33 'Will not cross git repository boundary %s when looking for ' |
| 34 'recipe_package.pyl' % cwd) |
| 35 else: |
| 36 raise EnvironmentError( |
| 37 'Reached root of filesystem when looking for recipe_package.pyl') |
| 38 |
| 39 def simulation_test(package, args): |
| 40 from recipe_engine import simulation_test |
| 41 simulation_test.main(package, args=args.args) |
| 42 |
| 43 |
| 44 def lint(package, args): |
| 45 from recipe_engine import lint_test |
| 46 lint_test.main(package) |
| 47 |
| 48 |
| 49 def run(package, args): |
| 50 from recipe_engine import main as recipe_main |
| 51 from recipe_engine import loader |
| 52 from recipe_engine import package |
| 53 from recipe_engine.third_party import annotator |
| 54 |
| 55 def get_properties_from_args(args): |
| 56 properties = dict(x.split('=', 1) for x in args) |
| 57 for key, val in properties.iteritems(): |
| 58 try: |
| 59 properties[key] = ast.literal_eval(val) |
| 60 except (ValueError, SyntaxError): |
| 61 pass # If a value couldn't be evaluated, keep the string version |
| 62 return properties |
| 63 |
| 64 def get_properties_from_file(filename): |
| 65 properties_file = sys.stdin if filename == '-' else open(filename) |
| 66 properties = ast.literal_eval(properties_file.read()) |
| 67 assert isinstance(properties, dict) |
| 68 |
| 69 arg_properties = get_properties_from_args(args.properties) |
| 70 if args.properties_file and arg_properties: |
| 71 raise Exception( |
| 72 'Cannot specify both properties file and command-line properties') |
| 73 elif args.properties_file: |
| 74 properties = get_properties_from_file(args.properties_file) |
| 75 else: |
| 76 properties = arg_properties |
| 77 |
| 78 properties['recipe'] = args.recipe |
| 79 |
| 80 # TODO(luqui): Environment whitelist. Cf crbug.com/392992 |
| 81 os.environ['PYTHONUNBUFFERED'] = '1' |
| 82 os.environ['PYTHONIOENCODING'] = 'UTF-8' |
| 83 |
| 84 universe = loader.RecipeUniverse(package) |
| 85 |
| 86 workdir = (args.workdir or |
| 87 os.path.join(os.path.dirname(os.path.realpath(__file__)), 'workdir')) |
| 88 logging.info('Using %s as work directory' % workdir) |
| 89 if not os.path.exists(workdir): |
| 90 os.makedirs(workdir) |
| 91 |
| 92 old_cwd = os.getcwd() |
| 93 os.chdir(workdir) |
| 94 try: |
| 95 ret = recipe_main.run_steps( |
| 96 properties, annotator.StructuredAnnotationStream(), universe=universe) |
| 97 return ret.status_code |
| 98 finally: |
| 99 os.chdir(old_cwd) |
| 100 |
| 101 |
| 102 def roll(args): |
| 103 from recipe_engine import package |
| 104 pyl_path = get_package_pyl(args) |
| 105 context = package.PackageContext.from_pyl_path(pyl_path) |
| 106 package_spec = package.PackageSpec.load_pyl(pyl_path) |
| 107 package_spec_dump = package_spec.dump() |
| 108 |
| 109 for date, spec in package_spec.iterate_consistent_updates(context): |
| 110 with open(pyl_path, 'w') as fh: |
| 111 json.dump(spec.dump(), fh, indent=2, separators=(',', ': ')) |
| 112 print 'Wrote %s' % pyl_path |
| 113 |
| 114 updated_deps = { |
| 115 dep_id: dep['revision'] |
| 116 for dep_id, dep in spec.dump()['deps'].iteritems() |
| 117 if dep['revision'] != package_spec_dump['deps'][dep_id]['revision'] |
| 118 } |
| 119 print 'To commit this roll, run:' |
| 120 print ' '.join([ |
| 121 'git commit -a --date "%s" -m "Roll dependencies"' % date.isoformat(), |
| 122 ' '.join([ '-m "Roll %s to %s"' % (dep_id, rev) |
| 123 for dep_id, rev in sorted(updated_deps.iteritems())]), |
| 124 ]) |
| 125 |
| 126 break |
| 127 else: |
| 128 print 'No consistent rolls found' |
| 129 |
| 130 |
| 131 def main(): |
| 132 from recipe_engine import package |
| 133 |
| 134 parser = argparse.ArgumentParser(description='Do things with recipes.') |
| 135 |
| 136 parser.add_argument( |
| 137 '--package', |
| 138 help='Package to operate on (directory containing recipe_package.pyl)') |
| 139 parser.add_argument( |
| 140 '--verbose', '-v', action='store_true', |
| 141 help='Increase logging verboisty') |
| 142 parser.add_argument( |
| 143 '--bootstrap', action='store_true', |
| 144 help='Use current rather than pinned version of recipe engine') |
| 145 |
| 146 subp = parser.add_subparsers() |
| 147 |
| 148 simulation_test_p = subp.add_parser('simulation_test', |
| 149 help='Generate or check expectations by simulating with mock actions') |
| 150 simulation_test_p.set_defaults(command='simulation_test') |
| 151 simulation_test_p.add_argument('args', nargs=argparse.REMAINDER) |
| 152 |
| 153 lint_p = subp.add_parser( |
| 154 'lint', |
| 155 help='Check recipes for stylistic and hygenic issues') |
| 156 lint_p.set_defaults(command='lint') |
| 157 |
| 158 run_p = subp.add_parser( |
| 159 'run', |
| 160 help='Run a recipe locally') |
| 161 run_p.set_defaults(command='run') |
| 162 run_p.add_argument( |
| 163 '--properties-file', |
| 164 help='A file containing a json blob of properties') |
| 165 run_p.add_argument( |
| 166 '--workdir', |
| 167 help='The working directory of recipe execution') |
| 168 run_p.add_argument( |
| 169 'recipe', |
| 170 help='The recipe to execute') |
| 171 run_p.add_argument( |
| 172 'properties', nargs=argparse.REMAINDER, |
| 173 help='A list of property pairs; e.g. mastername=chromium.linux ' |
| 174 'issue=12345') |
| 175 |
| 176 roll_p = subp.add_parser( |
| 177 'roll', |
| 178 help='Roll dependencies of a recipe package forward') |
| 179 roll_p.set_defaults(command='roll') |
| 180 |
| 181 args = parser.parse_args() |
| 182 if args.verbose: |
| 183 logging.getLogger().setLevel(logging.INFO) |
| 184 |
| 185 package_deps = package.PackageDeps.create(get_package_pyl(args)) |
| 186 # We don't forward to pinned tool on rolling, because there might be new |
| 187 # things to know about since then, who knows? Easier to look backward than |
| 188 # forward. |
| 189 if not args.bootstrap and args.command != 'roll': |
| 190 tool = os.path.join( |
| 191 package_deps.get_package('recipe_engine').root_dir, 'recipes.py') |
| 192 cmd = [ sys.executable, '-u', tool, '--bootstrap' ] + sys.argv[1:] |
| 193 logging.info(cmd) |
| 194 return subprocess.call(cmd) |
| 195 |
| 196 if args.command == 'simulation_test': |
| 197 return simulation_test(package_deps, args) |
| 198 elif args.command == 'lint': |
| 199 return lint(package_deps, args) |
| 200 elif args.command == 'run': |
| 201 return run(package_deps, args) |
| 202 elif args.command == 'roll': |
| 203 return roll(args) |
| 204 else: |
| 205 print """Dear sir or madam, |
| 206 It has come to my attention that a quite impossible condition has come |
| 207 to pass in the specification you have issued a request for us to fulfill. |
| 208 It is with a heavy heart that I inform you that, at the present juncture, |
| 209 there is no conceivable next action to be taken upon your request, and as |
| 210 such, we have decided to abort the request with a nonzero status code. We |
| 211 hope that your larger goals have not been put at risk due to this |
| 212 unfortunate circumstance, and wish you the best in deciding the next action |
| 213 in your venture and larger life. |
| 214 |
| 215 Warmly, |
| 216 recipes.py |
| 217 """ |
| 218 return 1 |
| 219 |
| 220 return 0 |
| 221 |
| 222 if __name__ == '__main__': |
| 223 sys.exit(main()) |
OLD | NEW |