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 |