OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """ | 6 """ |
7 Tool to perform checkouts in one easy command line! | 7 Tool to perform checkouts in one easy command line! |
8 | 8 |
9 Usage: | 9 Usage: |
10 fetch <recipe> [--property=value [--property2=value2 ...]] | 10 fetch <config> [--property=value [--property2=value2 ...]] |
11 | 11 |
12 This script is a wrapper around various version control and repository | 12 This script is a wrapper around various version control and repository |
13 checkout commands. It requires a |recipe| name, fetches data from that | 13 checkout commands. It requires a |config| name, fetches data from that |
14 recipe in depot_tools/recipes, and then performs all necessary inits, | 14 config in depot_tools/fetch_configs, and then performs all necessary inits, |
15 checkouts, pulls, fetches, etc. | 15 checkouts, pulls, fetches, etc. |
16 | 16 |
17 Optional arguments may be passed on the command line in key-value pairs. | 17 Optional arguments may be passed on the command line in key-value pairs. |
18 These parameters will be passed through to the recipe's main method. | 18 These parameters will be passed through to the config's main method. |
19 """ | 19 """ |
20 | 20 |
21 import json | 21 import json |
22 import optparse | 22 import optparse |
23 import os | 23 import os |
24 import pipes | 24 import pipes |
25 import subprocess | 25 import subprocess |
26 import sys | 26 import sys |
27 import textwrap | 27 import textwrap |
28 | 28 |
29 from distutils import spawn | 29 from distutils import spawn |
30 | 30 |
31 | 31 |
32 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) | 32 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) |
33 | 33 |
34 ################################################# | 34 ################################################# |
35 # Checkout class definitions. | 35 # Checkout class definitions. |
36 ################################################# | 36 ################################################# |
37 class Checkout(object): | 37 class Checkout(object): |
38 """Base class for implementing different types of checkouts. | 38 """Base class for implementing different types of checkouts. |
39 | 39 |
40 Attributes: | 40 Attributes: |
41 |base|: the absolute path of the directory in which this script is run. | 41 |base|: the absolute path of the directory in which this script is run. |
42 |spec|: the spec for this checkout as returned by the recipe. Different | 42 |spec|: the spec for this checkout as returned by the config. Different |
43 subclasses will expect different keys in this dictionary. | 43 subclasses will expect different keys in this dictionary. |
44 |root|: the directory into which the checkout will be performed, as returned | 44 |root|: the directory into which the checkout will be performed, as returned |
45 by the recipe. This is a relative path from |base|. | 45 by the config. This is a relative path from |base|. |
46 """ | 46 """ |
47 def __init__(self, options, spec, root): | 47 def __init__(self, options, spec, root): |
48 self.base = os.getcwd() | 48 self.base = os.getcwd() |
49 self.options = options | 49 self.options = options |
50 self.spec = spec | 50 self.spec = spec |
51 self.root = root | 51 self.root = root |
52 | 52 |
53 def exists(self): | 53 def exists(self): |
54 pass | 54 pass |
55 | 55 |
(...skipping 159 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
215 | 215 |
216 ################################################# | 216 ################################################# |
217 # Utility function and file entry point. | 217 # Utility function and file entry point. |
218 ################################################# | 218 ################################################# |
219 def usage(msg=None): | 219 def usage(msg=None): |
220 """Print help and exit.""" | 220 """Print help and exit.""" |
221 if msg: | 221 if msg: |
222 print 'Error:', msg | 222 print 'Error:', msg |
223 | 223 |
224 print textwrap.dedent("""\ | 224 print textwrap.dedent("""\ |
225 usage: %s [options] <recipe> [--property=value [--property2=value2 ...]] | 225 usage: %s [options] <config> [--property=value [--property2=value2 ...]] |
226 | 226 |
227 This script can be used to download the Chromium sources. See | 227 This script can be used to download the Chromium sources. See |
228 http://www.chromium.org/developers/how-tos/get-the-code | 228 http://www.chromium.org/developers/how-tos/get-the-code |
229 for full usage instructions. | 229 for full usage instructions. |
230 | 230 |
231 Valid options: | 231 Valid options: |
232 -h, --help, help Print this message. | 232 -h, --help, help Print this message. |
233 --nohooks Don't run hooks after checkout. | 233 --nohooks Don't run hooks after checkout. |
234 -n, --dry-run Don't run commands, only print them. | 234 -n, --dry-run Don't run commands, only print them. |
235 --no-history Perform shallow clones, don't fetch the full git histo
ry. | 235 --no-history Perform shallow clones, don't fetch the full git histo
ry. |
236 | 236 |
237 Valid fetch recipes:""") % os.path.basename(sys.argv[0]) | 237 Valid fetch configs:""") % os.path.basename(sys.argv[0]) |
238 | 238 |
239 recipes_dir = os.path.join(SCRIPT_PATH, 'recipes') | 239 configs_dir = os.path.join(SCRIPT_PATH, 'fetch_configs') |
240 recipes = [f[:-3] for f in os.listdir(recipes_dir) if f.endswith('.py')] | 240 configs = [f[:-3] for f in os.listdir(configs_dir) if f.endswith('.py')] |
241 recipes.sort() | 241 configs.sort() |
242 for fname in recipes: | 242 for fname in configs: |
243 print ' ' + fname | 243 print ' ' + fname |
244 | 244 |
245 sys.exit(bool(msg)) | 245 sys.exit(bool(msg)) |
246 | 246 |
247 | 247 |
248 def handle_args(argv): | 248 def handle_args(argv): |
249 """Gets the recipe name from the command line arguments.""" | 249 """Gets the config name from the command line arguments.""" |
250 if len(argv) <= 1: | 250 if len(argv) <= 1: |
251 usage('Must specify a recipe.') | 251 usage('Must specify a config.') |
252 if argv[1] in ('-h', '--help', 'help'): | 252 if argv[1] in ('-h', '--help', 'help'): |
253 usage() | 253 usage() |
254 | 254 |
255 dry_run = False | 255 dry_run = False |
256 nohooks = False | 256 nohooks = False |
257 no_history = False | 257 no_history = False |
258 while len(argv) >= 2: | 258 while len(argv) >= 2: |
259 arg = argv[1] | 259 arg = argv[1] |
260 if not arg.startswith('-'): | 260 if not arg.startswith('-'): |
261 break | 261 break |
262 argv.pop(1) | 262 argv.pop(1) |
263 if arg in ('-n', '--dry-run'): | 263 if arg in ('-n', '--dry-run'): |
264 dry_run = True | 264 dry_run = True |
265 elif arg == '--nohooks': | 265 elif arg == '--nohooks': |
266 nohooks = True | 266 nohooks = True |
267 elif arg == '--no-history': | 267 elif arg == '--no-history': |
268 no_history = True | 268 no_history = True |
269 else: | 269 else: |
270 usage('Invalid option %s.' % arg) | 270 usage('Invalid option %s.' % arg) |
271 | 271 |
272 def looks_like_arg(arg): | 272 def looks_like_arg(arg): |
273 return arg.startswith('--') and arg.count('=') == 1 | 273 return arg.startswith('--') and arg.count('=') == 1 |
274 | 274 |
275 bad_parms = [x for x in argv[2:] if not looks_like_arg(x)] | 275 bad_parms = [x for x in argv[2:] if not looks_like_arg(x)] |
276 if bad_parms: | 276 if bad_parms: |
277 usage('Got bad arguments %s' % bad_parms) | 277 usage('Got bad arguments %s' % bad_parms) |
278 | 278 |
279 recipe = argv[1] | 279 config = argv[1] |
280 props = argv[2:] | 280 props = argv[2:] |
281 return ( | 281 return ( |
282 optparse.Values( | 282 optparse.Values( |
283 {'dry_run':dry_run, 'nohooks':nohooks, 'no_history': no_history }), | 283 {'dry_run':dry_run, 'nohooks':nohooks, 'no_history': no_history }), |
284 recipe, | 284 config, |
285 props) | 285 props) |
286 | 286 |
287 | 287 |
288 def run_recipe_fetch(recipe, props, aliased=False): | 288 def run_config_fetch(config, props, aliased=False): |
289 """Invoke a recipe's fetch method with the passed-through args | 289 """Invoke a config's fetch method with the passed-through args |
290 and return its json output as a python object.""" | 290 and return its json output as a python object.""" |
291 recipe_path = os.path.abspath(os.path.join(SCRIPT_PATH, 'recipes', recipe)) | 291 config_path = os.path.abspath( |
292 if not os.path.exists(recipe_path + '.py'): | 292 os.path.join(SCRIPT_PATH, 'fetch_configs', config)) |
293 print "Could not find a recipe for %s" % recipe | 293 if not os.path.exists(config_path + '.py'): |
| 294 print "Could not find a config for %s" % config |
294 sys.exit(1) | 295 sys.exit(1) |
295 | 296 |
296 cmd = [sys.executable, recipe_path + '.py', 'fetch'] + props | 297 cmd = [sys.executable, config_path + '.py', 'fetch'] + props |
297 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] | 298 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] |
298 | 299 |
299 spec = json.loads(result) | 300 spec = json.loads(result) |
300 if 'alias' in spec: | 301 if 'alias' in spec: |
301 assert not aliased | 302 assert not aliased |
302 return run_recipe_fetch( | 303 return run_config_fetch( |
303 spec['alias']['recipe'], spec['alias']['props'] + props, aliased=True) | 304 spec['alias']['config'], spec['alias']['props'] + props, aliased=True) |
304 cmd = [sys.executable, recipe_path + '.py', 'root'] | 305 cmd = [sys.executable, config_path + '.py', 'root'] |
305 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] | 306 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] |
306 root = json.loads(result) | 307 root = json.loads(result) |
307 return spec, root | 308 return spec, root |
308 | 309 |
309 | 310 |
310 def run(options, spec, root): | 311 def run(options, spec, root): |
311 """Perform a checkout with the given type and configuration. | 312 """Perform a checkout with the given type and configuration. |
312 | 313 |
313 Args: | 314 Args: |
314 options: Options instance. | 315 options: Options instance. |
315 spec: Checkout configuration returned by the the recipe's fetch_spec | 316 spec: Checkout configuration returned by the the config's fetch_spec |
316 method (checkout type, repository url, etc.). | 317 method (checkout type, repository url, etc.). |
317 root: The directory into which the repo expects to be checkout out. | 318 root: The directory into which the repo expects to be checkout out. |
318 """ | 319 """ |
319 assert 'type' in spec | 320 assert 'type' in spec |
320 checkout_type = spec['type'] | 321 checkout_type = spec['type'] |
321 checkout_spec = spec['%s_spec' % checkout_type] | 322 checkout_spec = spec['%s_spec' % checkout_type] |
322 try: | 323 try: |
323 checkout = CheckoutFactory(checkout_type, options, checkout_spec, root) | 324 checkout = CheckoutFactory(checkout_type, options, checkout_spec, root) |
324 except KeyError: | 325 except KeyError: |
325 return 1 | 326 return 1 |
326 if checkout.exists(): | 327 if checkout.exists(): |
327 print 'Your current directory appears to already contain, or be part of, ' | 328 print 'Your current directory appears to already contain, or be part of, ' |
328 print 'a checkout. "fetch" is used only to get new checkouts. Use ' | 329 print 'a checkout. "fetch" is used only to get new checkouts. Use ' |
329 print '"gclient sync" to update existing checkouts.' | 330 print '"gclient sync" to update existing checkouts.' |
330 print | 331 print |
331 print 'Fetch also does not yet deal with partial checkouts, so if fetch' | 332 print 'Fetch also does not yet deal with partial checkouts, so if fetch' |
332 print 'failed, delete the checkout and start over (crbug.com/230691).' | 333 print 'failed, delete the checkout and start over (crbug.com/230691).' |
333 return 1 | 334 return 1 |
334 return checkout.init() | 335 return checkout.init() |
335 | 336 |
336 | 337 |
337 def main(): | 338 def main(): |
338 options, recipe, props = handle_args(sys.argv) | 339 options, config, props = handle_args(sys.argv) |
339 spec, root = run_recipe_fetch(recipe, props) | 340 spec, root = run_config_fetch(config, props) |
340 return run(options, spec, root) | 341 return run(options, spec, root) |
341 | 342 |
342 | 343 |
343 if __name__ == '__main__': | 344 if __name__ == '__main__': |
344 try: | 345 try: |
345 sys.exit(main()) | 346 sys.exit(main()) |
346 except KeyboardInterrupt: | 347 except KeyboardInterrupt: |
347 sys.stderr.write('interrupted\n') | 348 sys.stderr.write('interrupted\n') |
348 sys.exit(1) | 349 sys.exit(1) |
OLD | NEW |