Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(504)

Side by Side Diff: recipes.py

Issue 1344583003: Recipe package system. (Closed) Base URL: git@github.com:luci/recipes-py.git@master
Patch Set: Recompiled proto Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « recipe_modules/step/example.expected/warning.json ('k') | unittests/stdlib_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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())
OLDNEW
« no previous file with comments | « recipe_modules/step/example.expected/warning.json ('k') | unittests/stdlib_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698