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