blob: 04be95fa48ea0d095692a321c0e6b14ec5fedb57 [file] [log] [blame]
[email protected]cc023502013-04-03 20:24:211#!/usr/bin/env python
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
4# found in the LICENSE file.
5
6"""
7Tool to perform checkouts in one easy command line!
8
9Usage:
10 fetch <recipe> [--property=value [--property2=value2 ...]]
11
12This script is a wrapper around various version control and repository
13checkout commands. It requires a |recipe| name, fetches data from that
14recipe in depot_tools/recipes, and then performs all necessary inits,
15checkouts, pulls, fetches, etc.
16
17Optional arguments may be passed on the command line in key-value pairs.
18These parameters will be passed through to the recipe's main method.
19"""
20
21import json
[email protected]3596d582013-12-13 17:07:3322import optparse
[email protected]cc023502013-04-03 20:24:2123import os
[email protected]cc2d3e32014-08-06 19:47:5424import pipes
[email protected]cc023502013-04-03 20:24:2125import subprocess
26import sys
[email protected]cc2d3e32014-08-06 19:47:5427import textwrap
[email protected]cc023502013-04-03 20:24:2128
[email protected]6cc97a12013-04-12 06:15:5829from distutils import spawn
30
[email protected]cc023502013-04-03 20:24:2131
32SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
33
[email protected]cc023502013-04-03 20:24:2134#################################################
35# Checkout class definitions.
36#################################################
37class Checkout(object):
38 """Base class for implementing different types of checkouts.
39
40 Attributes:
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
43 subclasses will expect different keys in this dictionary.
44 |root|: the directory into which the checkout will be performed, as returned
45 by the recipe. This is a relative path from |base|.
46 """
[email protected]3596d582013-12-13 17:07:3347 def __init__(self, options, spec, root):
[email protected]cc023502013-04-03 20:24:2148 self.base = os.getcwd()
[email protected]3596d582013-12-13 17:07:3349 self.options = options
[email protected]cc023502013-04-03 20:24:2150 self.spec = spec
51 self.root = root
52
53 def exists(self):
54 pass
55
56 def init(self):
57 pass
58
59 def sync(self):
60 pass
61
[email protected]6cc97a12013-04-12 06:15:5862 def run(self, cmd, **kwargs):
63 print 'Running: %s' % (' '.join(pipes.quote(x) for x in cmd))
[email protected]38e94612014-02-12 22:19:4164 if self.options.dry_run:
[email protected]6cc97a12013-04-12 06:15:5865 return 0
66 return subprocess.check_call(cmd, **kwargs)
67
[email protected]cc023502013-04-03 20:24:2168
69class GclientCheckout(Checkout):
70
[email protected]d88d7f52013-04-03 21:09:0771 def run_gclient(self, *cmd, **kwargs):
[email protected]6cc97a12013-04-12 06:15:5872 if not spawn.find_executable('gclient'):
73 cmd_prefix = (sys.executable, os.path.join(SCRIPT_PATH, 'gclient.py'))
74 else:
75 cmd_prefix = ('gclient',)
76 return self.run(cmd_prefix + cmd, **kwargs)
[email protected]cc023502013-04-03 20:24:2177
78
79class GitCheckout(Checkout):
80
[email protected]d88d7f52013-04-03 21:09:0781 def run_git(self, *cmd, **kwargs):
[email protected]6cc97a12013-04-12 06:15:5882 if sys.platform == 'win32' and not spawn.find_executable('git'):
[email protected]cc2b6a12014-02-20 17:42:5983 git_path = os.path.join(SCRIPT_PATH, 'git.bat')
[email protected]6cc97a12013-04-12 06:15:5884 else:
85 git_path = 'git'
86 return self.run((git_path,) + cmd, **kwargs)
[email protected]cc023502013-04-03 20:24:2187
88
[email protected]2560ea72013-04-04 01:22:3889class SvnCheckout(Checkout):
90
91 def run_svn(self, *cmd, **kwargs):
[email protected]6cc97a12013-04-12 06:15:5892 if sys.platform == 'win32' and not spawn.find_executable('svn'):
93 svn_path = os.path.join(SCRIPT_PATH, 'svn_bin', 'svn.exe')
94 else:
95 svn_path = 'svn'
96 return self.run((svn_path,) + cmd, **kwargs)
[email protected]2560ea72013-04-04 01:22:3897
98
[email protected]d993e782013-04-11 20:03:1399class GclientGitCheckout(GclientCheckout, GitCheckout):
[email protected]cc023502013-04-03 20:24:21100
[email protected]3596d582013-12-13 17:07:33101 def __init__(self, options, spec, root):
102 super(GclientGitCheckout, self).__init__(options, spec, root)
[email protected]cc023502013-04-03 20:24:21103 assert 'solutions' in self.spec
[email protected]5bde64e2014-11-25 22:15:26104
105 def _format_spec(self):
106 def _format_literal(lit):
107 if isinstance(lit, basestring):
108 return '"%s"' % lit
109 if isinstance(lit, list):
110 return '[%s]' % ', '.join(_format_literal(i) for i in lit)
111 return '%r' % lit
112 soln_strings = []
113 for soln in self.spec['solutions']:
114 soln_string= '\n'.join(' "%s": %s,' % (key, _format_literal(value))
115 for key, value in soln.iteritems())
116 soln_strings.append(' {\n%s\n },' % soln_string)
117 gclient_spec = 'solutions = [\n%s\n]\n' % '\n'.join(soln_strings)
118 extra_keys = ['target_os', 'target_os_only']
119 gclient_spec += ''.join('%s = %s\n' % (key, _format_literal(self.spec[key]))
120 for key in extra_keys if key in self.spec)
121 return gclient_spec
[email protected]cc023502013-04-03 20:24:21122
123 def exists(self):
124 return os.path.exists(os.path.join(os.getcwd(), self.root))
125
126 def init(self):
[email protected]cc023502013-04-03 20:24:21127 # Configure and do the gclient checkout.
[email protected]5bde64e2014-11-25 22:15:26128 self.run_gclient('config', '--spec', self._format_spec())
[email protected]048da082014-05-06 08:32:40129 sync_cmd = ['sync']
[email protected]3596d582013-12-13 17:07:33130 if self.options.nohooks:
[email protected]048da082014-05-06 08:32:40131 sync_cmd.append('--nohooks')
[email protected]5439ea52014-08-06 17:18:18132 if self.options.no_history:
133 sync_cmd.append('--no-history')
[email protected]048da082014-05-06 08:32:40134 if self.spec.get('with_branch_heads', False):
135 sync_cmd.append('--with_branch_heads')
136 self.run_gclient(*sync_cmd)
[email protected]cc023502013-04-03 20:24:21137
138 # Configure git.
139 wd = os.path.join(self.base, self.root)
[email protected]38e94612014-02-12 22:19:41140 if self.options.dry_run:
[email protected]2560ea72013-04-04 01:22:38141 print 'cd %s' % wd
[email protected]cc023502013-04-03 20:24:21142 self.run_git(
143 'submodule', 'foreach',
144 'git config -f $toplevel/.git/config submodule.$name.ignore all',
145 cwd=wd)
[email protected]f2fb5e72014-04-03 02:36:44146 self.run_git(
147 'config', '--add', 'remote.origin.fetch',
148 '+refs/tags/*:refs/tags/*', cwd=wd)
[email protected]cc023502013-04-03 20:24:21149 self.run_git('config', 'diff.ignoreSubmodules', 'all', cwd=wd)
150
[email protected]d993e782013-04-11 20:03:13151
152class GclientGitSvnCheckout(GclientGitCheckout, SvnCheckout):
153
[email protected]3596d582013-12-13 17:07:33154 def __init__(self, options, spec, root):
155 super(GclientGitSvnCheckout, self).__init__(options, spec, root)
[email protected]d993e782013-04-11 20:03:13156
157 def init(self):
158 # Ensure we are authenticated with subversion for all submodules.
159 git_svn_dirs = json.loads(self.spec.get('submodule_git_svn_spec', '{}'))
160 git_svn_dirs.update({self.root: self.spec})
161 for _, svn_spec in git_svn_dirs.iteritems():
[email protected]14f633b2014-10-22 10:35:33162 if svn_spec.get('svn_url'):
163 try:
164 self.run_svn('ls', '--non-interactive', svn_spec['svn_url'])
165 except subprocess.CalledProcessError:
166 print 'Please run `svn ls %s`' % svn_spec['svn_url']
167 return 1
[email protected]d993e782013-04-11 20:03:13168
169 super(GclientGitSvnCheckout, self).init()
170
[email protected]cc023502013-04-03 20:24:21171 # Configure git-svn.
[email protected]2560ea72013-04-04 01:22:38172 for path, svn_spec in git_svn_dirs.iteritems():
173 real_path = os.path.join(*path.split('/'))
174 if real_path != self.root:
175 real_path = os.path.join(self.root, real_path)
176 wd = os.path.join(self.base, real_path)
[email protected]38e94612014-02-12 22:19:41177 if self.options.dry_run:
[email protected]2560ea72013-04-04 01:22:38178 print 'cd %s' % wd
[email protected]14f633b2014-10-22 10:35:33179 if svn_spec.get('auto'):
180 self.run_git('auto-svn', cwd=wd)
181 continue
182 self.run_git('svn', 'init', svn_spec['svn_url'], cwd=wd)
183 self.run_git('config', '--unset-all', 'svn-remote.svn.fetch', cwd=wd)
184 for svn_branch, git_ref in svn_spec.get('git_svn_fetch', {}).items():
185 self.run_git('config', '--add', 'svn-remote.svn.fetch',
186 '%s:%s' % (svn_branch, git_ref), cwd=wd)
187 for svn_branch, git_ref in svn_spec.get('git_svn_branches', {}).items():
188 self.run_git('config', '--add', 'svn-remote.svn.branches',
189 '%s:%s' % (svn_branch, git_ref), cwd=wd)
[email protected]cc023502013-04-03 20:24:21190 self.run_git('svn', 'fetch', cwd=wd)
191
192
[email protected]2560ea72013-04-04 01:22:38193
[email protected]cc023502013-04-03 20:24:21194CHECKOUT_TYPE_MAP = {
195 'gclient': GclientCheckout,
[email protected]d993e782013-04-11 20:03:13196 'gclient_git': GclientGitCheckout,
[email protected]cc023502013-04-03 20:24:21197 'gclient_git_svn': GclientGitSvnCheckout,
198 'git': GitCheckout,
199}
200
201
[email protected]3596d582013-12-13 17:07:33202def CheckoutFactory(type_name, options, spec, root):
[email protected]cc023502013-04-03 20:24:21203 """Factory to build Checkout class instances."""
204 class_ = CHECKOUT_TYPE_MAP.get(type_name)
205 if not class_:
206 raise KeyError('unrecognized checkout type: %s' % type_name)
[email protected]3596d582013-12-13 17:07:33207 return class_(options, spec, root)
[email protected]cc023502013-04-03 20:24:21208
209
210#################################################
211# Utility function and file entry point.
212#################################################
213def usage(msg=None):
214 """Print help and exit."""
215 if msg:
216 print 'Error:', msg
217
[email protected]cc2d3e32014-08-06 19:47:54218 print textwrap.dedent("""\
219 usage: %s [options] <recipe> [--property=value [--property2=value2 ...]]
[email protected]3596d582013-12-13 17:07:33220
[email protected]cc2d3e32014-08-06 19:47:54221 This script can be used to download the Chromium sources. See
222 http://www.chromium.org/developers/how-tos/get-the-code
223 for full usage instructions.
[email protected]3596d582013-12-13 17:07:33224
[email protected]cc2d3e32014-08-06 19:47:54225 Valid options:
226 -h, --help, help Print this message.
227 --nohooks Don't run hooks after checkout.
228 -n, --dry-run Don't run commands, only print them.
229 --no-history Perform shallow clones, don't fetch the full git history.
230
231 Valid fetch recipes:""") % os.path.basename(sys.argv[0])
232 for fname in os.listdir(os.path.join(SCRIPT_PATH, 'recipes')):
233 if fname.endswith('.py'):
234 print ' ' + fname[:-3]
235
[email protected]cc023502013-04-03 20:24:21236 sys.exit(bool(msg))
237
238
239def handle_args(argv):
240 """Gets the recipe name from the command line arguments."""
241 if len(argv) <= 1:
242 usage('Must specify a recipe.')
[email protected]e3d147d2013-04-03 20:31:27243 if argv[1] in ('-h', '--help', 'help'):
244 usage()
[email protected]cc023502013-04-03 20:24:21245
[email protected]38e94612014-02-12 22:19:41246 dry_run = False
[email protected]3596d582013-12-13 17:07:33247 nohooks = False
[email protected]5439ea52014-08-06 17:18:18248 no_history = False
[email protected]3596d582013-12-13 17:07:33249 while len(argv) >= 2:
250 arg = argv[1]
251 if not arg.startswith('-'):
252 break
[email protected]d88d7f52013-04-03 21:09:07253 argv.pop(1)
[email protected]3596d582013-12-13 17:07:33254 if arg in ('-n', '--dry-run'):
[email protected]38e94612014-02-12 22:19:41255 dry_run = True
[email protected]3596d582013-12-13 17:07:33256 elif arg == '--nohooks':
257 nohooks = True
[email protected]5439ea52014-08-06 17:18:18258 elif arg == '--no-history':
259 no_history = True
[email protected]3596d582013-12-13 17:07:33260 else:
261 usage('Invalid option %s.' % arg)
[email protected]d88d7f52013-04-03 21:09:07262
[email protected]cc023502013-04-03 20:24:21263 def looks_like_arg(arg):
264 return arg.startswith('--') and arg.count('=') == 1
265
266 bad_parms = [x for x in argv[2:] if not looks_like_arg(x)]
267 if bad_parms:
268 usage('Got bad arguments %s' % bad_parms)
269
270 recipe = argv[1]
271 props = argv[2:]
[email protected]5439ea52014-08-06 17:18:18272 return (
273 optparse.Values(
274 {'dry_run':dry_run, 'nohooks':nohooks, 'no_history': no_history }),
275 recipe,
276 props)
[email protected]cc023502013-04-03 20:24:21277
278
279def run_recipe_fetch(recipe, props, aliased=False):
280 """Invoke a recipe's fetch method with the passed-through args
281 and return its json output as a python object."""
282 recipe_path = os.path.abspath(os.path.join(SCRIPT_PATH, 'recipes', recipe))
[email protected]a992edb2013-04-03 21:22:20283 if not os.path.exists(recipe_path + '.py'):
[email protected]2bf328a2013-04-03 21:14:41284 print "Could not find a recipe for %s" % recipe
285 sys.exit(1)
286
[email protected]cc023502013-04-03 20:24:21287 cmd = [sys.executable, recipe_path + '.py', 'fetch'] + props
288 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
[email protected]2bf328a2013-04-03 21:14:41289
[email protected]cc023502013-04-03 20:24:21290 spec = json.loads(result)
291 if 'alias' in spec:
292 assert not aliased
293 return run_recipe_fetch(
294 spec['alias']['recipe'], spec['alias']['props'] + props, aliased=True)
295 cmd = [sys.executable, recipe_path + '.py', 'root']
296 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
297 root = json.loads(result)
298 return spec, root
299
300
[email protected]3596d582013-12-13 17:07:33301def run(options, spec, root):
[email protected]cc023502013-04-03 20:24:21302 """Perform a checkout with the given type and configuration.
303
304 Args:
[email protected]3596d582013-12-13 17:07:33305 options: Options instance.
[email protected]cc023502013-04-03 20:24:21306 spec: Checkout configuration returned by the the recipe's fetch_spec
307 method (checkout type, repository url, etc.).
308 root: The directory into which the repo expects to be checkout out.
309 """
310 assert 'type' in spec
311 checkout_type = spec['type']
312 checkout_spec = spec['%s_spec' % checkout_type]
313 try:
[email protected]3596d582013-12-13 17:07:33314 checkout = CheckoutFactory(checkout_type, options, checkout_spec, root)
[email protected]cc023502013-04-03 20:24:21315 except KeyError:
316 return 1
317 if checkout.exists():
[email protected]fd79e0d2013-04-12 21:34:32318 print 'You appear to already have a checkout. "fetch" is used only'
319 print 'to get new checkouts. Use "gclient sync" to update the checkout.'
320 print
321 print 'Fetch also does not yet deal with partial checkouts, so if fetch'
322 print 'failed, delete the checkout and start over (crbug.com/230691).'
[email protected]cc023502013-04-03 20:24:21323 return 1
[email protected]2560ea72013-04-04 01:22:38324 return checkout.init()
[email protected]cc023502013-04-03 20:24:21325
326
327def main():
[email protected]3596d582013-12-13 17:07:33328 options, recipe, props = handle_args(sys.argv)
[email protected]cc023502013-04-03 20:24:21329 spec, root = run_recipe_fetch(recipe, props)
[email protected]3596d582013-12-13 17:07:33330 return run(options, spec, root)
[email protected]cc023502013-04-03 20:24:21331
332
333if __name__ == '__main__':
[email protected]013731e2015-02-26 18:28:43334 try:
335 sys.exit(main())
336 except KeyboardInterrupt:
337 sys.stderr.write('interrupted\n')
338 sys.exit(1)