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