blob: cf34404e4c1a15b1353869c7489cb1462a3e04a8 [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:
[email protected]b371a1c2015-12-04 01:42:4810 fetch <config> [--property=value [--property2=value2 ...]]
[email protected]cc023502013-04-03 20:24:2111
12This script is a wrapper around various version control and repository
[email protected]b371a1c2015-12-04 01:42:4813checkout commands. It requires a |config| name, fetches data from that
14config in depot_tools/fetch_configs, and then performs all necessary inits,
[email protected]cc023502013-04-03 20:24:2115checkouts, pulls, fetches, etc.
16
17Optional arguments may be passed on the command line in key-value pairs.
[email protected]b371a1c2015-12-04 01:42:4818These parameters will be passed through to the config's main method.
[email protected]cc023502013-04-03 20:24:2119"""
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.
[email protected]b371a1c2015-12-04 01:42:4842 |spec|: the spec for this checkout as returned by the config. Different
[email protected]cc023502013-04-03 20:24:2143 subclasses will expect different keys in this dictionary.
44 |root|: the directory into which the checkout will be performed, as returned
[email protected]b371a1c2015-12-04 01:42:4845 by the config. This is a relative path from |base|.
[email protected]cc023502013-04-03 20:24:2146 """
[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]294c7832015-06-17 16:16:3265 return ''
[email protected]5a447762015-06-10 20:01:3966 return subprocess.check_output(cmd, **kwargs)
[email protected]6cc97a12013-04-12 06:15:5867
[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
[email protected]5a447762015-06-10 20:01:3978 def exists(self):
79 try:
80 gclient_root = self.run_gclient('root').strip()
81 return (os.path.exists(os.path.join(gclient_root, '.gclient')) or
82 os.path.exists(os.path.join(os.getcwd(), self.root)))
83 except subprocess.CalledProcessError:
84 pass
85 return os.path.exists(os.path.join(os.getcwd(), self.root))
86
[email protected]cc023502013-04-03 20:24:2187
88class GitCheckout(Checkout):
89
[email protected]d88d7f52013-04-03 21:09:0790 def run_git(self, *cmd, **kwargs):
[email protected]6cc97a12013-04-12 06:15:5891 if sys.platform == 'win32' and not spawn.find_executable('git'):
[email protected]cc2b6a12014-02-20 17:42:5992 git_path = os.path.join(SCRIPT_PATH, 'git.bat')
[email protected]6cc97a12013-04-12 06:15:5893 else:
94 git_path = 'git'
95 return self.run((git_path,) + cmd, **kwargs)
[email protected]cc023502013-04-03 20:24:2196
97
[email protected]2560ea72013-04-04 01:22:3898class SvnCheckout(Checkout):
99
100 def run_svn(self, *cmd, **kwargs):
[email protected]6cc97a12013-04-12 06:15:58101 if sys.platform == 'win32' and not spawn.find_executable('svn'):
102 svn_path = os.path.join(SCRIPT_PATH, 'svn_bin', 'svn.exe')
103 else:
104 svn_path = 'svn'
105 return self.run((svn_path,) + cmd, **kwargs)
[email protected]2560ea72013-04-04 01:22:38106
107
[email protected]d993e782013-04-11 20:03:13108class GclientGitCheckout(GclientCheckout, GitCheckout):
[email protected]cc023502013-04-03 20:24:21109
[email protected]3596d582013-12-13 17:07:33110 def __init__(self, options, spec, root):
111 super(GclientGitCheckout, self).__init__(options, spec, root)
[email protected]cc023502013-04-03 20:24:21112 assert 'solutions' in self.spec
[email protected]5bde64e2014-11-25 22:15:26113
114 def _format_spec(self):
115 def _format_literal(lit):
116 if isinstance(lit, basestring):
117 return '"%s"' % lit
118 if isinstance(lit, list):
119 return '[%s]' % ', '.join(_format_literal(i) for i in lit)
120 return '%r' % lit
121 soln_strings = []
122 for soln in self.spec['solutions']:
123 soln_string= '\n'.join(' "%s": %s,' % (key, _format_literal(value))
124 for key, value in soln.iteritems())
125 soln_strings.append(' {\n%s\n },' % soln_string)
126 gclient_spec = 'solutions = [\n%s\n]\n' % '\n'.join(soln_strings)
127 extra_keys = ['target_os', 'target_os_only']
128 gclient_spec += ''.join('%s = %s\n' % (key, _format_literal(self.spec[key]))
129 for key in extra_keys if key in self.spec)
130 return gclient_spec
[email protected]cc023502013-04-03 20:24:21131
[email protected]cc023502013-04-03 20:24:21132 def init(self):
133 # Configure and do the gclient checkout.
[email protected]5bde64e2014-11-25 22:15:26134 self.run_gclient('config', '--spec', self._format_spec())
[email protected]048da082014-05-06 08:32:40135 sync_cmd = ['sync']
[email protected]b98f3f22015-06-15 21:59:09136 if self.options.nohooks:
[email protected]048da082014-05-06 08:32:40137 sync_cmd.append('--nohooks')
[email protected]5439ea52014-08-06 17:18:18138 if self.options.no_history:
139 sync_cmd.append('--no-history')
[email protected]048da082014-05-06 08:32:40140 if self.spec.get('with_branch_heads', False):
141 sync_cmd.append('--with_branch_heads')
142 self.run_gclient(*sync_cmd)
[email protected]cc023502013-04-03 20:24:21143
144 # Configure git.
145 wd = os.path.join(self.base, self.root)
[email protected]38e94612014-02-12 22:19:41146 if self.options.dry_run:
[email protected]2560ea72013-04-04 01:22:38147 print 'cd %s' % wd
[email protected]cc023502013-04-03 20:24:21148 self.run_git(
149 'submodule', 'foreach',
150 'git config -f $toplevel/.git/config submodule.$name.ignore all',
151 cwd=wd)
[email protected]f2fb5e72014-04-03 02:36:44152 self.run_git(
153 'config', '--add', 'remote.origin.fetch',
154 '+refs/tags/*:refs/tags/*', cwd=wd)
[email protected]cc023502013-04-03 20:24:21155 self.run_git('config', 'diff.ignoreSubmodules', 'all', cwd=wd)
156
[email protected]d993e782013-04-11 20:03:13157
158class GclientGitSvnCheckout(GclientGitCheckout, SvnCheckout):
159
[email protected]3596d582013-12-13 17:07:33160 def __init__(self, options, spec, root):
161 super(GclientGitSvnCheckout, self).__init__(options, spec, root)
[email protected]d993e782013-04-11 20:03:13162
163 def init(self):
164 # Ensure we are authenticated with subversion for all submodules.
165 git_svn_dirs = json.loads(self.spec.get('submodule_git_svn_spec', '{}'))
166 git_svn_dirs.update({self.root: self.spec})
167 for _, svn_spec in git_svn_dirs.iteritems():
[email protected]14f633b2014-10-22 10:35:33168 if svn_spec.get('svn_url'):
169 try:
170 self.run_svn('ls', '--non-interactive', svn_spec['svn_url'])
171 except subprocess.CalledProcessError:
172 print 'Please run `svn ls %s`' % svn_spec['svn_url']
173 return 1
[email protected]d993e782013-04-11 20:03:13174
175 super(GclientGitSvnCheckout, self).init()
176
[email protected]cc023502013-04-03 20:24:21177 # Configure git-svn.
[email protected]2560ea72013-04-04 01:22:38178 for path, svn_spec in git_svn_dirs.iteritems():
179 real_path = os.path.join(*path.split('/'))
180 if real_path != self.root:
181 real_path = os.path.join(self.root, real_path)
182 wd = os.path.join(self.base, real_path)
[email protected]38e94612014-02-12 22:19:41183 if self.options.dry_run:
[email protected]2560ea72013-04-04 01:22:38184 print 'cd %s' % wd
[email protected]14f633b2014-10-22 10:35:33185 if svn_spec.get('auto'):
186 self.run_git('auto-svn', cwd=wd)
187 continue
188 self.run_git('svn', 'init', svn_spec['svn_url'], cwd=wd)
189 self.run_git('config', '--unset-all', 'svn-remote.svn.fetch', cwd=wd)
190 for svn_branch, git_ref in svn_spec.get('git_svn_fetch', {}).items():
191 self.run_git('config', '--add', 'svn-remote.svn.fetch',
192 '%s:%s' % (svn_branch, git_ref), cwd=wd)
193 for svn_branch, git_ref in svn_spec.get('git_svn_branches', {}).items():
194 self.run_git('config', '--add', 'svn-remote.svn.branches',
195 '%s:%s' % (svn_branch, git_ref), cwd=wd)
[email protected]cc023502013-04-03 20:24:21196 self.run_git('svn', 'fetch', cwd=wd)
197
198
[email protected]2560ea72013-04-04 01:22:38199
[email protected]cc023502013-04-03 20:24:21200CHECKOUT_TYPE_MAP = {
201 'gclient': GclientCheckout,
[email protected]d993e782013-04-11 20:03:13202 'gclient_git': GclientGitCheckout,
[email protected]cc023502013-04-03 20:24:21203 'gclient_git_svn': GclientGitSvnCheckout,
204 'git': GitCheckout,
205}
206
207
[email protected]3596d582013-12-13 17:07:33208def CheckoutFactory(type_name, options, spec, root):
[email protected]cc023502013-04-03 20:24:21209 """Factory to build Checkout class instances."""
210 class_ = CHECKOUT_TYPE_MAP.get(type_name)
211 if not class_:
212 raise KeyError('unrecognized checkout type: %s' % type_name)
[email protected]3596d582013-12-13 17:07:33213 return class_(options, spec, root)
[email protected]cc023502013-04-03 20:24:21214
215
216#################################################
217# Utility function and file entry point.
218#################################################
219def usage(msg=None):
220 """Print help and exit."""
221 if msg:
222 print 'Error:', msg
223
[email protected]cc2d3e32014-08-06 19:47:54224 print textwrap.dedent("""\
[email protected]b371a1c2015-12-04 01:42:48225 usage: %s [options] <config> [--property=value [--property2=value2 ...]]
[email protected]3596d582013-12-13 17:07:33226
[email protected]cc2d3e32014-08-06 19:47:54227 This script can be used to download the Chromium sources. See
228 http://www.chromium.org/developers/how-tos/get-the-code
229 for full usage instructions.
[email protected]3596d582013-12-13 17:07:33230
[email protected]cc2d3e32014-08-06 19:47:54231 Valid options:
232 -h, --help, help Print this message.
233 --nohooks Don't run hooks after checkout.
234 -n, --dry-run Don't run commands, only print them.
235 --no-history Perform shallow clones, don't fetch the full git history.
236
[email protected]b371a1c2015-12-04 01:42:48237 Valid fetch configs:""") % os.path.basename(sys.argv[0])
[email protected]37103c92015-09-19 20:54:39238
[email protected]b371a1c2015-12-04 01:42:48239 configs_dir = os.path.join(SCRIPT_PATH, 'fetch_configs')
240 configs = [f[:-3] for f in os.listdir(configs_dir) if f.endswith('.py')]
241 configs.sort()
242 for fname in configs:
[email protected]37103c92015-09-19 20:54:39243 print ' ' + fname
[email protected]cc2d3e32014-08-06 19:47:54244
[email protected]cc023502013-04-03 20:24:21245 sys.exit(bool(msg))
246
247
248def handle_args(argv):
[email protected]b371a1c2015-12-04 01:42:48249 """Gets the config name from the command line arguments."""
[email protected]cc023502013-04-03 20:24:21250 if len(argv) <= 1:
[email protected]b371a1c2015-12-04 01:42:48251 usage('Must specify a config.')
[email protected]e3d147d2013-04-03 20:31:27252 if argv[1] in ('-h', '--help', 'help'):
253 usage()
[email protected]cc023502013-04-03 20:24:21254
[email protected]38e94612014-02-12 22:19:41255 dry_run = False
[email protected]3596d582013-12-13 17:07:33256 nohooks = False
[email protected]5439ea52014-08-06 17:18:18257 no_history = False
[email protected]3596d582013-12-13 17:07:33258 while len(argv) >= 2:
259 arg = argv[1]
260 if not arg.startswith('-'):
261 break
[email protected]d88d7f52013-04-03 21:09:07262 argv.pop(1)
[email protected]3596d582013-12-13 17:07:33263 if arg in ('-n', '--dry-run'):
[email protected]38e94612014-02-12 22:19:41264 dry_run = True
[email protected]3596d582013-12-13 17:07:33265 elif arg == '--nohooks':
266 nohooks = True
[email protected]5439ea52014-08-06 17:18:18267 elif arg == '--no-history':
268 no_history = True
[email protected]3596d582013-12-13 17:07:33269 else:
270 usage('Invalid option %s.' % arg)
[email protected]d88d7f52013-04-03 21:09:07271
[email protected]cc023502013-04-03 20:24:21272 def looks_like_arg(arg):
273 return arg.startswith('--') and arg.count('=') == 1
274
275 bad_parms = [x for x in argv[2:] if not looks_like_arg(x)]
276 if bad_parms:
277 usage('Got bad arguments %s' % bad_parms)
278
[email protected]b371a1c2015-12-04 01:42:48279 config = argv[1]
[email protected]cc023502013-04-03 20:24:21280 props = argv[2:]
[email protected]5439ea52014-08-06 17:18:18281 return (
282 optparse.Values(
283 {'dry_run':dry_run, 'nohooks':nohooks, 'no_history': no_history }),
[email protected]b371a1c2015-12-04 01:42:48284 config,
[email protected]5439ea52014-08-06 17:18:18285 props)
[email protected]cc023502013-04-03 20:24:21286
287
[email protected]b371a1c2015-12-04 01:42:48288def run_config_fetch(config, props, aliased=False):
289 """Invoke a config's fetch method with the passed-through args
[email protected]cc023502013-04-03 20:24:21290 and return its json output as a python object."""
[email protected]b371a1c2015-12-04 01:42:48291 config_path = os.path.abspath(
292 os.path.join(SCRIPT_PATH, 'fetch_configs', config))
293 if not os.path.exists(config_path + '.py'):
294 print "Could not find a config for %s" % config
[email protected]2bf328a2013-04-03 21:14:41295 sys.exit(1)
296
[email protected]b371a1c2015-12-04 01:42:48297 cmd = [sys.executable, config_path + '.py', 'fetch'] + props
[email protected]cc023502013-04-03 20:24:21298 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
[email protected]2bf328a2013-04-03 21:14:41299
[email protected]cc023502013-04-03 20:24:21300 spec = json.loads(result)
301 if 'alias' in spec:
302 assert not aliased
[email protected]b371a1c2015-12-04 01:42:48303 return run_config_fetch(
304 spec['alias']['config'], spec['alias']['props'] + props, aliased=True)
305 cmd = [sys.executable, config_path + '.py', 'root']
[email protected]cc023502013-04-03 20:24:21306 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
307 root = json.loads(result)
308 return spec, root
309
310
[email protected]3596d582013-12-13 17:07:33311def run(options, spec, root):
[email protected]cc023502013-04-03 20:24:21312 """Perform a checkout with the given type and configuration.
313
314 Args:
[email protected]3596d582013-12-13 17:07:33315 options: Options instance.
[email protected]b371a1c2015-12-04 01:42:48316 spec: Checkout configuration returned by the the config's fetch_spec
[email protected]cc023502013-04-03 20:24:21317 method (checkout type, repository url, etc.).
318 root: The directory into which the repo expects to be checkout out.
319 """
320 assert 'type' in spec
321 checkout_type = spec['type']
322 checkout_spec = spec['%s_spec' % checkout_type]
323 try:
[email protected]3596d582013-12-13 17:07:33324 checkout = CheckoutFactory(checkout_type, options, checkout_spec, root)
[email protected]cc023502013-04-03 20:24:21325 except KeyError:
326 return 1
327 if checkout.exists():
[email protected]5a447762015-06-10 20:01:39328 print 'Your current directory appears to already contain, or be part of, '
329 print 'a checkout. "fetch" is used only to get new checkouts. Use '
330 print '"gclient sync" to update existing checkouts.'
[email protected]fd79e0d2013-04-12 21:34:32331 print
332 print 'Fetch also does not yet deal with partial checkouts, so if fetch'
333 print 'failed, delete the checkout and start over (crbug.com/230691).'
[email protected]cc023502013-04-03 20:24:21334 return 1
[email protected]2560ea72013-04-04 01:22:38335 return checkout.init()
[email protected]cc023502013-04-03 20:24:21336
337
338def main():
[email protected]b371a1c2015-12-04 01:42:48339 options, config, props = handle_args(sys.argv)
340 spec, root = run_config_fetch(config, props)
[email protected]3596d582013-12-13 17:07:33341 return run(options, spec, root)
[email protected]cc023502013-04-03 20:24:21342
343
344if __name__ == '__main__':
[email protected]013731e2015-02-26 18:28:43345 try:
346 sys.exit(main())
347 except KeyboardInterrupt:
348 sys.stderr.write('interrupted\n')
349 sys.exit(1)