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.
7 Tool to perform checkouts in one easy command line!
10 fetch <recipe> [--property=value [--property2=value2 ...]]
12 This script is a wrapper around various version control and repository
13 checkout commands. It requires a |recipe| name, fetches data from that
14 recipe in depot_tools/recipes, and then performs all necessary inits,
15 checkouts, pulls, fetches, etc.
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.
29 from distutils import spawn
32 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
34 #################################################
35 # Checkout class definitions.
36 #################################################
37 class Checkout(object):
38 """Base class for implementing different types of checkouts.
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|.
47 def __init__(self, options, spec, root):
48 self.base = os.getcwd()
49 self.options = options
62 def run(self, cmd, **kwargs):
63 print 'Running: %s' % (' '.join(pipes.quote(x) for x in cmd))
64 if self.options.dry_run:
66 return subprocess.check_call(cmd, **kwargs)
69 class GclientCheckout(Checkout):
71 def run_gclient(self, *cmd, **kwargs):
72 if not spawn.find_executable('gclient'):
73 cmd_prefix = (sys.executable, os.path.join(SCRIPT_PATH, 'gclient.py'))
75 cmd_prefix = ('gclient',)
76 return self.run(cmd_prefix + cmd, **kwargs)
79 class GitCheckout(Checkout):
81 def run_git(self, *cmd, **kwargs):
82 if sys.platform == 'win32' and not spawn.find_executable('git'):
83 git_path = os.path.join(SCRIPT_PATH, 'git.bat')
86 return self.run((git_path,) + cmd, **kwargs)
89 class SvnCheckout(Checkout):
91 def run_svn(self, *cmd, **kwargs):
92 if sys.platform == 'win32' and not spawn.find_executable('svn'):
93 svn_path = os.path.join(SCRIPT_PATH, 'svn_bin', 'svn.exe')
96 return self.run((svn_path,) + cmd, **kwargs)
99 class GclientGitCheckout(GclientCheckout, GitCheckout):
101 def __init__(self, options, spec, root):
102 super(GclientGitCheckout, self).__init__(options, spec, root)
103 assert 'solutions' in self.spec
105 def _format_spec(self):
106 def _format_literal(lit):
107 if isinstance(lit, basestring):
109 if isinstance(lit, list):
110 return '[%s]' % ', '.join(_format_literal(i) for i in lit)
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)
124 return os.path.exists(os.path.join(os.getcwd(), self.root))
127 # Configure and do the gclient checkout.
128 self.run_gclient('config', '--spec', self._format_spec())
130 if self.options.nohooks:
131 sync_cmd.append('--nohooks')
132 if self.options.no_history:
133 sync_cmd.append('--no-history')
134 if self.spec.get('with_branch_heads', False):
135 sync_cmd.append('--with_branch_heads')
136 self.run_gclient(*sync_cmd)
139 wd = os.path.join(self.base, self.root)
140 if self.options.dry_run:
143 'submodule', 'foreach',
144 'git config -f $toplevel/.git/config submodule.$name.ignore all',
147 'config', '--add', 'remote.origin.fetch',
148 '+refs/tags/*:refs/tags/*', cwd=wd)
149 self.run_git('config', 'diff.ignoreSubmodules', 'all', cwd=wd)
152 class GclientGitSvnCheckout(GclientGitCheckout, SvnCheckout):
154 def __init__(self, options, spec, root):
155 super(GclientGitSvnCheckout, self).__init__(options, spec, root)
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():
162 if svn_spec.get('svn_url'):
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']
169 super(GclientGitSvnCheckout, self).init()
172 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)
177 if self.options.dry_run:
179 if svn_spec.get('auto'):
180 self.run_git('auto-svn', cwd=wd)
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)
190 self.run_git('svn', 'fetch', cwd=wd)
194 CHECKOUT_TYPE_MAP = {
195 'gclient': GclientCheckout,
196 'gclient_git': GclientGitCheckout,
197 'gclient_git_svn': GclientGitSvnCheckout,
202 def CheckoutFactory(type_name, options, spec, root):
203 """Factory to build Checkout class instances."""
204 class_ = CHECKOUT_TYPE_MAP.get(type_name)
206 raise KeyError('unrecognized checkout type: %s' % type_name)
207 return class_(options, spec, root)
210 #################################################
211 # Utility function and file entry point.
212 #################################################
214 """Print help and exit."""
218 print textwrap.dedent("""\
219 usage: %s [options] <recipe> [--property=value [--property2=value2 ...]]
221 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.
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.
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]
239 def handle_args(argv):
240 """Gets the recipe name from the command line arguments."""
242 usage('Must specify a recipe.')
243 if argv[1] in ('-h', '--help', 'help'):
249 while len(argv) >= 2:
251 if not arg.startswith('-'):
254 if arg in ('-n', '--dry-run'):
256 elif arg == '--nohooks':
258 elif arg == '--no-history':
261 usage('Invalid option %s.' % arg)
263 def looks_like_arg(arg):
264 return arg.startswith('--') and arg.count('=') == 1
266 bad_parms = [x for x in argv[2:] if not looks_like_arg(x)]
268 usage('Got bad arguments %s' % bad_parms)
274 {'dry_run':dry_run, 'nohooks':nohooks, 'no_history': no_history }),
279 def 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))
283 if not os.path.exists(recipe_path + '.py'):
284 print "Could not find a recipe for %s" % recipe
287 cmd = [sys.executable, recipe_path + '.py', 'fetch'] + props
288 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
290 spec = json.loads(result)
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)
301 def run(options, spec, root):
302 """Perform a checkout with the given type and configuration.
305 options: Options instance.
306 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.
310 assert 'type' in spec
311 checkout_type = spec['type']
312 checkout_spec = spec['%s_spec' % checkout_type]
314 checkout = CheckoutFactory(checkout_type, options, checkout_spec, root)
317 if checkout.exists():
318 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.'
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).'
324 return checkout.init()
328 options, recipe, props = handle_args(sys.argv)
329 spec, root = run_recipe_fetch(recipe, props)
330 return run(options, spec, root)
333 if __name__ == '__main__':