blob: c78dfe722b9ca060075b647d8c1624c364375c66 [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
22import os
23import subprocess
24import sys
25import pipes
26
[email protected]6cc97a12013-04-12 06:15:5827from distutils import spawn
28
[email protected]cc023502013-04-03 20:24:2129
30SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
31
32
33#################################################
34# Checkout class definitions.
35#################################################
36class Checkout(object):
37 """Base class for implementing different types of checkouts.
38
39 Attributes:
40 |base|: the absolute path of the directory in which this script is run.
41 |spec|: the spec for this checkout as returned by the recipe. Different
42 subclasses will expect different keys in this dictionary.
43 |root|: the directory into which the checkout will be performed, as returned
44 by the recipe. This is a relative path from |base|.
45 """
[email protected]d88d7f52013-04-03 21:09:0746 def __init__(self, dryrun, spec, root):
[email protected]cc023502013-04-03 20:24:2147 self.base = os.getcwd()
[email protected]d88d7f52013-04-03 21:09:0748 self.dryrun = dryrun
[email protected]cc023502013-04-03 20:24:2149 self.spec = spec
50 self.root = root
51
52 def exists(self):
53 pass
54
55 def init(self):
56 pass
57
58 def sync(self):
59 pass
60
[email protected]6cc97a12013-04-12 06:15:5861 def run(self, cmd, **kwargs):
62 print 'Running: %s' % (' '.join(pipes.quote(x) for x in cmd))
63 if self.dryrun:
64 return 0
65 return subprocess.check_call(cmd, **kwargs)
66
[email protected]cc023502013-04-03 20:24:2167
68class GclientCheckout(Checkout):
69
[email protected]d88d7f52013-04-03 21:09:0770 def run_gclient(self, *cmd, **kwargs):
[email protected]6cc97a12013-04-12 06:15:5871 if not spawn.find_executable('gclient'):
72 cmd_prefix = (sys.executable, os.path.join(SCRIPT_PATH, 'gclient.py'))
73 else:
74 cmd_prefix = ('gclient',)
75 return self.run(cmd_prefix + cmd, **kwargs)
[email protected]cc023502013-04-03 20:24:2176
77
78class GitCheckout(Checkout):
79
[email protected]d88d7f52013-04-03 21:09:0780 def run_git(self, *cmd, **kwargs):
[email protected]6cc97a12013-04-12 06:15:5881 if sys.platform == 'win32' and not spawn.find_executable('git'):
82 git_path = os.path.join(SCRIPT_PATH, 'git-1.8.0_bin', 'bin', 'git.exe')
83 else:
84 git_path = 'git'
85 return self.run((git_path,) + cmd, **kwargs)
[email protected]cc023502013-04-03 20:24:2186
87
[email protected]2560ea72013-04-04 01:22:3888class SvnCheckout(Checkout):
89
90 def run_svn(self, *cmd, **kwargs):
[email protected]6cc97a12013-04-12 06:15:5891 if sys.platform == 'win32' and not spawn.find_executable('svn'):
92 svn_path = os.path.join(SCRIPT_PATH, 'svn_bin', 'svn.exe')
93 else:
94 svn_path = 'svn'
95 return self.run((svn_path,) + cmd, **kwargs)
[email protected]2560ea72013-04-04 01:22:3896
97
[email protected]d993e782013-04-11 20:03:1398class GclientGitCheckout(GclientCheckout, GitCheckout):
[email protected]cc023502013-04-03 20:24:2199
[email protected]d88d7f52013-04-03 21:09:07100 def __init__(self, dryrun, spec, root):
[email protected]d993e782013-04-11 20:03:13101 super(GclientGitCheckout, self).__init__(dryrun, spec, root)
[email protected]cc023502013-04-03 20:24:21102 assert 'solutions' in self.spec
103 keys = ['solutions', 'target_os', 'target_os_only']
104 gclient_spec = '\n'.join('%s = %s' % (key, self.spec[key])
[email protected]c4209082013-04-18 18:33:52105 for key in keys if key in self.spec)
[email protected]cc023502013-04-03 20:24:21106 self.spec['gclient_spec'] = gclient_spec
[email protected]cc023502013-04-03 20:24:21107
108 def exists(self):
109 return os.path.exists(os.path.join(os.getcwd(), self.root))
110
111 def init(self):
[email protected]8623da32013-04-04 17:36:01112 # TODO(dpranke): Work around issues w/ delta compression on big repos.
113 self.run_git('config', '--global', 'core.deltaBaseCacheLimit', '1G')
114
[email protected]cc023502013-04-03 20:24:21115 # Configure and do the gclient checkout.
116 self.run_gclient('config', '--spec', self.spec['gclient_spec'])
117 self.run_gclient('sync')
118
119 # Configure git.
120 wd = os.path.join(self.base, self.root)
[email protected]7ca51b32013-04-03 21:29:50121 if self.dryrun:
[email protected]2560ea72013-04-04 01:22:38122 print 'cd %s' % wd
[email protected]cc023502013-04-03 20:24:21123 self.run_git(
124 'submodule', 'foreach',
125 'git config -f $toplevel/.git/config submodule.$name.ignore all',
126 cwd=wd)
127 self.run_git('config', 'diff.ignoreSubmodules', 'all', cwd=wd)
128
[email protected]d993e782013-04-11 20:03:13129
130class GclientGitSvnCheckout(GclientGitCheckout, SvnCheckout):
131
132 def __init__(self, dryrun, spec, root):
133 super(GclientGitSvnCheckout, self).__init__(dryrun, spec, root)
134 assert 'svn_url' in self.spec
135 assert 'svn_branch' in self.spec
136 assert 'svn_ref' in self.spec
137
138 def init(self):
139 # Ensure we are authenticated with subversion for all submodules.
140 git_svn_dirs = json.loads(self.spec.get('submodule_git_svn_spec', '{}'))
141 git_svn_dirs.update({self.root: self.spec})
142 for _, svn_spec in git_svn_dirs.iteritems():
143 try:
144 self.run_svn('ls', '--non-interactive', svn_spec['svn_url'])
145 except subprocess.CalledProcessError:
146 print 'Please run `svn ls %s`' % svn_spec['svn_url']
147 return 1
148
149 super(GclientGitSvnCheckout, self).init()
150
[email protected]cc023502013-04-03 20:24:21151 # Configure git-svn.
[email protected]2560ea72013-04-04 01:22:38152 for path, svn_spec in git_svn_dirs.iteritems():
153 real_path = os.path.join(*path.split('/'))
154 if real_path != self.root:
155 real_path = os.path.join(self.root, real_path)
156 wd = os.path.join(self.base, real_path)
[email protected]7ca51b32013-04-03 21:29:50157 if self.dryrun:
[email protected]2560ea72013-04-04 01:22:38158 print 'cd %s' % wd
[email protected]cc023502013-04-03 20:24:21159 self.run_git('svn', 'init', '--prefix=origin/', '-T',
[email protected]2560ea72013-04-04 01:22:38160 svn_spec['svn_branch'], svn_spec['svn_url'], cwd=wd)
[email protected]cc023502013-04-03 20:24:21161 self.run_git('config', '--replace', 'svn-remote.svn.fetch',
[email protected]2560ea72013-04-04 01:22:38162 svn_spec['svn_branch'] + ':refs/remotes/origin/' +
163 svn_spec['svn_ref'], cwd=wd)
[email protected]cc023502013-04-03 20:24:21164 self.run_git('svn', 'fetch', cwd=wd)
165
166
[email protected]2560ea72013-04-04 01:22:38167
[email protected]cc023502013-04-03 20:24:21168CHECKOUT_TYPE_MAP = {
169 'gclient': GclientCheckout,
[email protected]d993e782013-04-11 20:03:13170 'gclient_git': GclientGitCheckout,
[email protected]cc023502013-04-03 20:24:21171 'gclient_git_svn': GclientGitSvnCheckout,
172 'git': GitCheckout,
173}
174
175
[email protected]d88d7f52013-04-03 21:09:07176def CheckoutFactory(type_name, dryrun, spec, root):
[email protected]cc023502013-04-03 20:24:21177 """Factory to build Checkout class instances."""
178 class_ = CHECKOUT_TYPE_MAP.get(type_name)
179 if not class_:
180 raise KeyError('unrecognized checkout type: %s' % type_name)
[email protected]d88d7f52013-04-03 21:09:07181 return class_(dryrun, spec, root)
[email protected]cc023502013-04-03 20:24:21182
183
184#################################################
185# Utility function and file entry point.
186#################################################
187def usage(msg=None):
188 """Print help and exit."""
189 if msg:
190 print 'Error:', msg
191
192 print (
193"""
[email protected]d88d7f52013-04-03 21:09:07194usage: %s [-n|--dry-run] <recipe> [--property=value [--property2=value2 ...]]
[email protected]cc023502013-04-03 20:24:21195""" % os.path.basename(sys.argv[0]))
196 sys.exit(bool(msg))
197
198
199def handle_args(argv):
200 """Gets the recipe name from the command line arguments."""
201 if len(argv) <= 1:
202 usage('Must specify a recipe.')
[email protected]e3d147d2013-04-03 20:31:27203 if argv[1] in ('-h', '--help', 'help'):
204 usage()
[email protected]cc023502013-04-03 20:24:21205
[email protected]a992edb2013-04-03 21:22:20206 dryrun = False
[email protected]d88d7f52013-04-03 21:09:07207 if argv[1] in ('-n', '--dry-run'):
208 dryrun = True
209 argv.pop(1)
210
[email protected]cc023502013-04-03 20:24:21211 def looks_like_arg(arg):
212 return arg.startswith('--') and arg.count('=') == 1
213
214 bad_parms = [x for x in argv[2:] if not looks_like_arg(x)]
215 if bad_parms:
216 usage('Got bad arguments %s' % bad_parms)
217
218 recipe = argv[1]
219 props = argv[2:]
[email protected]d88d7f52013-04-03 21:09:07220 return dryrun, recipe, props
[email protected]cc023502013-04-03 20:24:21221
222
223def run_recipe_fetch(recipe, props, aliased=False):
224 """Invoke a recipe's fetch method with the passed-through args
225 and return its json output as a python object."""
226 recipe_path = os.path.abspath(os.path.join(SCRIPT_PATH, 'recipes', recipe))
[email protected]a992edb2013-04-03 21:22:20227 if not os.path.exists(recipe_path + '.py'):
[email protected]2bf328a2013-04-03 21:14:41228 print "Could not find a recipe for %s" % recipe
229 sys.exit(1)
230
[email protected]cc023502013-04-03 20:24:21231 cmd = [sys.executable, recipe_path + '.py', 'fetch'] + props
232 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
[email protected]2bf328a2013-04-03 21:14:41233
[email protected]cc023502013-04-03 20:24:21234 spec = json.loads(result)
235 if 'alias' in spec:
236 assert not aliased
237 return run_recipe_fetch(
238 spec['alias']['recipe'], spec['alias']['props'] + props, aliased=True)
239 cmd = [sys.executable, recipe_path + '.py', 'root']
240 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
241 root = json.loads(result)
242 return spec, root
243
244
[email protected]d88d7f52013-04-03 21:09:07245def run(dryrun, spec, root):
[email protected]cc023502013-04-03 20:24:21246 """Perform a checkout with the given type and configuration.
247
248 Args:
[email protected]d88d7f52013-04-03 21:09:07249 dryrun: if True, don't actually execute the commands
[email protected]cc023502013-04-03 20:24:21250 spec: Checkout configuration returned by the the recipe's fetch_spec
251 method (checkout type, repository url, etc.).
252 root: The directory into which the repo expects to be checkout out.
253 """
254 assert 'type' in spec
255 checkout_type = spec['type']
256 checkout_spec = spec['%s_spec' % checkout_type]
257 try:
[email protected]d88d7f52013-04-03 21:09:07258 checkout = CheckoutFactory(checkout_type, dryrun, checkout_spec, root)
[email protected]cc023502013-04-03 20:24:21259 except KeyError:
260 return 1
261 if checkout.exists():
[email protected]fd79e0d2013-04-12 21:34:32262 print 'You appear to already have a checkout. "fetch" is used only'
263 print 'to get new checkouts. Use "gclient sync" to update the checkout.'
264 print
265 print 'Fetch also does not yet deal with partial checkouts, so if fetch'
266 print 'failed, delete the checkout and start over (crbug.com/230691).'
[email protected]cc023502013-04-03 20:24:21267 return 1
[email protected]2560ea72013-04-04 01:22:38268 return checkout.init()
[email protected]cc023502013-04-03 20:24:21269
270
271def main():
[email protected]d88d7f52013-04-03 21:09:07272 dryrun, recipe, props = handle_args(sys.argv)
[email protected]cc023502013-04-03 20:24:21273 spec, root = run_recipe_fetch(recipe, props)
[email protected]d88d7f52013-04-03 21:09:07274 return run(dryrun, spec, root)
[email protected]cc023502013-04-03 20:24:21275
276
277if __name__ == '__main__':
278 sys.exit(main())