blob: d911c0668c47be3fbc459a57ff86874bef965b47 [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
Dan Jacques209a6812017-07-12 18:40:2029import git_common
30
[email protected]6cc97a12013-04-12 06:15:5831from distutils import spawn
32
[email protected]cc023502013-04-03 20:24:2133
34SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
35
[email protected]cc023502013-04-03 20:24:2136#################################################
37# Checkout class definitions.
38#################################################
39class Checkout(object):
40 """Base class for implementing different types of checkouts.
41
42 Attributes:
43 |base|: the absolute path of the directory in which this script is run.
[email protected]b371a1c2015-12-04 01:42:4844 |spec|: the spec for this checkout as returned by the config. Different
[email protected]cc023502013-04-03 20:24:2145 subclasses will expect different keys in this dictionary.
46 |root|: the directory into which the checkout will be performed, as returned
[email protected]b371a1c2015-12-04 01:42:4847 by the config. This is a relative path from |base|.
[email protected]cc023502013-04-03 20:24:2148 """
[email protected]3596d582013-12-13 17:07:3349 def __init__(self, options, spec, root):
[email protected]cc023502013-04-03 20:24:2150 self.base = os.getcwd()
[email protected]3596d582013-12-13 17:07:3351 self.options = options
[email protected]cc023502013-04-03 20:24:2152 self.spec = spec
53 self.root = root
54
55 def exists(self):
56 pass
57
58 def init(self):
59 pass
60
61 def sync(self):
62 pass
63
Edward Lemur7a4ced22018-01-26 15:26:0564 def run(self, cmd, return_stdout=False, **kwargs):
[email protected]6cc97a12013-04-12 06:15:5865 print 'Running: %s' % (' '.join(pipes.quote(x) for x in cmd))
[email protected]38e94612014-02-12 22:19:4166 if self.options.dry_run:
[email protected]294c7832015-06-17 16:16:3267 return ''
Edward Lemur7a4ced22018-01-26 15:26:0568 if return_stdout:
69 return subprocess.check_output(cmd, **kwargs)
70 else:
71 subprocess.check_call(cmd, **kwargs)
72 return ''
[email protected]6cc97a12013-04-12 06:15:5873
[email protected]cc023502013-04-03 20:24:2174
75class GclientCheckout(Checkout):
76
[email protected]d88d7f52013-04-03 21:09:0777 def run_gclient(self, *cmd, **kwargs):
[email protected]6cc97a12013-04-12 06:15:5878 if not spawn.find_executable('gclient'):
79 cmd_prefix = (sys.executable, os.path.join(SCRIPT_PATH, 'gclient.py'))
80 else:
81 cmd_prefix = ('gclient',)
82 return self.run(cmd_prefix + cmd, **kwargs)
[email protected]cc023502013-04-03 20:24:2183
[email protected]5a447762015-06-10 20:01:3984 def exists(self):
85 try:
Edward Lemur7a4ced22018-01-26 15:26:0586 gclient_root = self.run_gclient('root', return_stdout=True).strip()
[email protected]5a447762015-06-10 20:01:3987 return (os.path.exists(os.path.join(gclient_root, '.gclient')) or
88 os.path.exists(os.path.join(os.getcwd(), self.root)))
89 except subprocess.CalledProcessError:
90 pass
91 return os.path.exists(os.path.join(os.getcwd(), self.root))
92
[email protected]cc023502013-04-03 20:24:2193
94class GitCheckout(Checkout):
95
[email protected]d88d7f52013-04-03 21:09:0796 def run_git(self, *cmd, **kwargs):
Dan Jacques209a6812017-07-12 18:40:2097 print 'Running: git %s' % (' '.join(pipes.quote(x) for x in cmd))
Aaron Gablebd95f412017-11-29 19:20:2698 if self.options.dry_run:
99 return ''
Dan Jacques209a6812017-07-12 18:40:20100 return git_common.run(*cmd, **kwargs)
[email protected]cc023502013-04-03 20:24:21101
102
[email protected]d993e782013-04-11 20:03:13103class GclientGitCheckout(GclientCheckout, GitCheckout):
[email protected]cc023502013-04-03 20:24:21104
[email protected]3596d582013-12-13 17:07:33105 def __init__(self, options, spec, root):
106 super(GclientGitCheckout, self).__init__(options, spec, root)
[email protected]cc023502013-04-03 20:24:21107 assert 'solutions' in self.spec
[email protected]5bde64e2014-11-25 22:15:26108
109 def _format_spec(self):
110 def _format_literal(lit):
111 if isinstance(lit, basestring):
112 return '"%s"' % lit
113 if isinstance(lit, list):
114 return '[%s]' % ', '.join(_format_literal(i) for i in lit)
115 return '%r' % lit
116 soln_strings = []
117 for soln in self.spec['solutions']:
118 soln_string= '\n'.join(' "%s": %s,' % (key, _format_literal(value))
119 for key, value in soln.iteritems())
120 soln_strings.append(' {\n%s\n },' % soln_string)
121 gclient_spec = 'solutions = [\n%s\n]\n' % '\n'.join(soln_strings)
Dr Alex Gouaillarde1dd46f2016-11-28 07:00:04122 extra_keys = ['target_os', 'target_os_only', 'cache_dir']
[email protected]5bde64e2014-11-25 22:15:26123 gclient_spec += ''.join('%s = %s\n' % (key, _format_literal(self.spec[key]))
124 for key in extra_keys if key in self.spec)
125 return gclient_spec
[email protected]cc023502013-04-03 20:24:21126
[email protected]cc023502013-04-03 20:24:21127 def init(self):
128 # Configure and do the gclient checkout.
[email protected]5bde64e2014-11-25 22:15:26129 self.run_gclient('config', '--spec', self._format_spec())
[email protected]048da082014-05-06 08:32:40130 sync_cmd = ['sync']
[email protected]b98f3f22015-06-15 21:59:09131 if self.options.nohooks:
[email protected]048da082014-05-06 08:32:40132 sync_cmd.append('--nohooks')
[email protected]5439ea52014-08-06 17:18:18133 if self.options.no_history:
134 sync_cmd.append('--no-history')
[email protected]048da082014-05-06 08:32:40135 if self.spec.get('with_branch_heads', False):
136 sync_cmd.append('--with_branch_heads')
137 self.run_gclient(*sync_cmd)
[email protected]cc023502013-04-03 20:24:21138
139 # Configure git.
140 wd = os.path.join(self.base, self.root)
[email protected]38e94612014-02-12 22:19:41141 if self.options.dry_run:
[email protected]2560ea72013-04-04 01:22:38142 print 'cd %s' % wd
[email protected]cc023502013-04-03 20:24:21143 self.run_git(
144 'submodule', 'foreach',
145 'git config -f $toplevel/.git/config submodule.$name.ignore all',
146 cwd=wd)
Torne (Richard Coles)08ca04b2018-02-08 20:23:08147 if not self.options.no_history:
148 self.run_git(
149 'config', '--add', 'remote.origin.fetch',
150 '+refs/tags/*:refs/tags/*', cwd=wd)
[email protected]cc023502013-04-03 20:24:21151 self.run_git('config', 'diff.ignoreSubmodules', 'all', cwd=wd)
152
[email protected]d993e782013-04-11 20:03:13153
[email protected]cc023502013-04-03 20:24:21154CHECKOUT_TYPE_MAP = {
155 'gclient': GclientCheckout,
[email protected]d993e782013-04-11 20:03:13156 'gclient_git': GclientGitCheckout,
[email protected]cc023502013-04-03 20:24:21157 'git': GitCheckout,
158}
159
160
[email protected]3596d582013-12-13 17:07:33161def CheckoutFactory(type_name, options, spec, root):
[email protected]cc023502013-04-03 20:24:21162 """Factory to build Checkout class instances."""
163 class_ = CHECKOUT_TYPE_MAP.get(type_name)
164 if not class_:
165 raise KeyError('unrecognized checkout type: %s' % type_name)
[email protected]3596d582013-12-13 17:07:33166 return class_(options, spec, root)
[email protected]cc023502013-04-03 20:24:21167
168
169#################################################
170# Utility function and file entry point.
171#################################################
172def usage(msg=None):
173 """Print help and exit."""
174 if msg:
175 print 'Error:', msg
176
[email protected]cc2d3e32014-08-06 19:47:54177 print textwrap.dedent("""\
[email protected]b371a1c2015-12-04 01:42:48178 usage: %s [options] <config> [--property=value [--property2=value2 ...]]
[email protected]3596d582013-12-13 17:07:33179
[email protected]cc2d3e32014-08-06 19:47:54180 This script can be used to download the Chromium sources. See
181 http://www.chromium.org/developers/how-tos/get-the-code
182 for full usage instructions.
[email protected]3596d582013-12-13 17:07:33183
[email protected]cc2d3e32014-08-06 19:47:54184 Valid options:
185 -h, --help, help Print this message.
186 --nohooks Don't run hooks after checkout.
[email protected]78faf8b2016-05-09 23:26:37187 --force (dangerous) Don't look for existing .gclient file.
[email protected]cc2d3e32014-08-06 19:47:54188 -n, --dry-run Don't run commands, only print them.
189 --no-history Perform shallow clones, don't fetch the full git history.
190
[email protected]b371a1c2015-12-04 01:42:48191 Valid fetch configs:""") % os.path.basename(sys.argv[0])
[email protected]37103c92015-09-19 20:54:39192
[email protected]b371a1c2015-12-04 01:42:48193 configs_dir = os.path.join(SCRIPT_PATH, 'fetch_configs')
194 configs = [f[:-3] for f in os.listdir(configs_dir) if f.endswith('.py')]
195 configs.sort()
196 for fname in configs:
[email protected]37103c92015-09-19 20:54:39197 print ' ' + fname
[email protected]cc2d3e32014-08-06 19:47:54198
[email protected]cc023502013-04-03 20:24:21199 sys.exit(bool(msg))
200
201
202def handle_args(argv):
[email protected]b371a1c2015-12-04 01:42:48203 """Gets the config name from the command line arguments."""
[email protected]cc023502013-04-03 20:24:21204 if len(argv) <= 1:
[email protected]b371a1c2015-12-04 01:42:48205 usage('Must specify a config.')
[email protected]e3d147d2013-04-03 20:31:27206 if argv[1] in ('-h', '--help', 'help'):
207 usage()
[email protected]cc023502013-04-03 20:24:21208
[email protected]38e94612014-02-12 22:19:41209 dry_run = False
[email protected]3596d582013-12-13 17:07:33210 nohooks = False
[email protected]5439ea52014-08-06 17:18:18211 no_history = False
[email protected]78faf8b2016-05-09 23:26:37212 force = False
[email protected]3596d582013-12-13 17:07:33213 while len(argv) >= 2:
214 arg = argv[1]
215 if not arg.startswith('-'):
216 break
[email protected]d88d7f52013-04-03 21:09:07217 argv.pop(1)
[email protected]3596d582013-12-13 17:07:33218 if arg in ('-n', '--dry-run'):
[email protected]38e94612014-02-12 22:19:41219 dry_run = True
[email protected]3596d582013-12-13 17:07:33220 elif arg == '--nohooks':
221 nohooks = True
[email protected]5439ea52014-08-06 17:18:18222 elif arg == '--no-history':
223 no_history = True
[email protected]78faf8b2016-05-09 23:26:37224 elif arg == '--force':
225 force = True
[email protected]3596d582013-12-13 17:07:33226 else:
227 usage('Invalid option %s.' % arg)
[email protected]d88d7f52013-04-03 21:09:07228
[email protected]cc023502013-04-03 20:24:21229 def looks_like_arg(arg):
230 return arg.startswith('--') and arg.count('=') == 1
231
232 bad_parms = [x for x in argv[2:] if not looks_like_arg(x)]
233 if bad_parms:
234 usage('Got bad arguments %s' % bad_parms)
235
[email protected]b371a1c2015-12-04 01:42:48236 config = argv[1]
[email protected]cc023502013-04-03 20:24:21237 props = argv[2:]
[email protected]5439ea52014-08-06 17:18:18238 return (
[email protected]78faf8b2016-05-09 23:26:37239 optparse.Values({
240 'dry_run': dry_run,
241 'nohooks': nohooks,
242 'no_history': no_history,
243 'force': force}),
[email protected]b371a1c2015-12-04 01:42:48244 config,
[email protected]5439ea52014-08-06 17:18:18245 props)
[email protected]cc023502013-04-03 20:24:21246
247
[email protected]b371a1c2015-12-04 01:42:48248def run_config_fetch(config, props, aliased=False):
249 """Invoke a config's fetch method with the passed-through args
[email protected]cc023502013-04-03 20:24:21250 and return its json output as a python object."""
[email protected]b371a1c2015-12-04 01:42:48251 config_path = os.path.abspath(
252 os.path.join(SCRIPT_PATH, 'fetch_configs', config))
253 if not os.path.exists(config_path + '.py'):
254 print "Could not find a config for %s" % config
[email protected]2bf328a2013-04-03 21:14:41255 sys.exit(1)
256
[email protected]b371a1c2015-12-04 01:42:48257 cmd = [sys.executable, config_path + '.py', 'fetch'] + props
[email protected]cc023502013-04-03 20:24:21258 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
[email protected]2bf328a2013-04-03 21:14:41259
[email protected]cc023502013-04-03 20:24:21260 spec = json.loads(result)
261 if 'alias' in spec:
262 assert not aliased
[email protected]b371a1c2015-12-04 01:42:48263 return run_config_fetch(
264 spec['alias']['config'], spec['alias']['props'] + props, aliased=True)
265 cmd = [sys.executable, config_path + '.py', 'root']
[email protected]cc023502013-04-03 20:24:21266 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
267 root = json.loads(result)
268 return spec, root
269
270
[email protected]3596d582013-12-13 17:07:33271def run(options, spec, root):
[email protected]cc023502013-04-03 20:24:21272 """Perform a checkout with the given type and configuration.
273
274 Args:
[email protected]3596d582013-12-13 17:07:33275 options: Options instance.
[email protected]b371a1c2015-12-04 01:42:48276 spec: Checkout configuration returned by the the config's fetch_spec
[email protected]cc023502013-04-03 20:24:21277 method (checkout type, repository url, etc.).
278 root: The directory into which the repo expects to be checkout out.
279 """
280 assert 'type' in spec
281 checkout_type = spec['type']
282 checkout_spec = spec['%s_spec' % checkout_type]
283 try:
[email protected]3596d582013-12-13 17:07:33284 checkout = CheckoutFactory(checkout_type, options, checkout_spec, root)
[email protected]cc023502013-04-03 20:24:21285 except KeyError:
286 return 1
[email protected]78faf8b2016-05-09 23:26:37287 if not options.force and checkout.exists():
[email protected]5a447762015-06-10 20:01:39288 print 'Your current directory appears to already contain, or be part of, '
289 print 'a checkout. "fetch" is used only to get new checkouts. Use '
290 print '"gclient sync" to update existing checkouts.'
[email protected]fd79e0d2013-04-12 21:34:32291 print
292 print 'Fetch also does not yet deal with partial checkouts, so if fetch'
293 print 'failed, delete the checkout and start over (crbug.com/230691).'
[email protected]cc023502013-04-03 20:24:21294 return 1
[email protected]2560ea72013-04-04 01:22:38295 return checkout.init()
[email protected]cc023502013-04-03 20:24:21296
297
298def main():
[email protected]b371a1c2015-12-04 01:42:48299 options, config, props = handle_args(sys.argv)
300 spec, root = run_config_fetch(config, props)
[email protected]3596d582013-12-13 17:07:33301 return run(options, spec, root)
[email protected]cc023502013-04-03 20:24:21302
303
304if __name__ == '__main__':
[email protected]013731e2015-02-26 18:28:43305 try:
306 sys.exit(main())
307 except KeyboardInterrupt:
308 sys.stderr.write('interrupted\n')
309 sys.exit(1)