blob: 2109017b3da310fb244625f8caab1ca22c536bfb [file] [log] [blame]
agable@chromium.orgcc023502013-04-03 20:24:21 +00001#!/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
27
28SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
29
30
31#################################################
32# Checkout class definitions.
33#################################################
34class Checkout(object):
35 """Base class for implementing different types of checkouts.
36
37 Attributes:
38 |base|: the absolute path of the directory in which this script is run.
39 |spec|: the spec for this checkout as returned by the recipe. Different
40 subclasses will expect different keys in this dictionary.
41 |root|: the directory into which the checkout will be performed, as returned
42 by the recipe. This is a relative path from |base|.
43 """
44 def __init__(self, spec, root):
45 self.base = os.getcwd()
46 self.spec = spec
47 self.root = root
48
49 def exists(self):
50 pass
51
52 def init(self):
53 pass
54
55 def sync(self):
56 pass
57
58
59class GclientCheckout(Checkout):
60
61 @staticmethod
62 def run_gclient(*cmd, **kwargs):
63 print 'Running: gclient %s' % ' '.join(pipes.quote(x) for x in cmd)
64 return subprocess.check_call(('gclient',) + cmd, **kwargs)
65
66
67class GitCheckout(Checkout):
68
69 @staticmethod
70 def run_git(*cmd, **kwargs):
71 print 'Running: git %s' % ' '.join(pipes.quote(x) for x in cmd)
72 return subprocess.check_call(('git',) + cmd, **kwargs)
73
74
75class GclientGitSvnCheckout(GclientCheckout, GitCheckout):
76
77 def __init__(self, spec, root):
78 super(GclientGitSvnCheckout, self).__init__(spec, root)
79 assert 'solutions' in self.spec
80 keys = ['solutions', 'target_os', 'target_os_only']
81 gclient_spec = '\n'.join('%s = %s' % (key, self.spec[key])
82 for key in self.spec if key in keys)
83 self.spec['gclient_spec'] = gclient_spec
84 assert 'svn_url' in self.spec
85 assert 'svn_branch' in self.spec
86 assert 'svn_ref' in self.spec
87
88 def exists(self):
89 return os.path.exists(os.path.join(os.getcwd(), self.root))
90
91 def init(self):
92 # Configure and do the gclient checkout.
93 self.run_gclient('config', '--spec', self.spec['gclient_spec'])
94 self.run_gclient('sync')
95
96 # Configure git.
97 wd = os.path.join(self.base, self.root)
98 self.run_git(
99 'submodule', 'foreach',
100 'git config -f $toplevel/.git/config submodule.$name.ignore all',
101 cwd=wd)
102 self.run_git('config', 'diff.ignoreSubmodules', 'all', cwd=wd)
103
104 # Configure git-svn.
105 self.run_git('svn', 'init', '--prefix=origin/', '-T',
106 self.spec['svn_branch'], self.spec['svn_url'], cwd=wd)
107 self.run_git('config', 'svn-remote.svn.fetch', self.spec['svn_branch'] +
108 ':refs/remotes/origin/' + self.spec['svn_ref'], cwd=wd)
109 self.run_git('svn', 'fetch', cwd=wd)
110
111 # Configure git-svn submodules, if any.
112 submodules = json.loads(self.spec.get('submodule_git_svn_spec', '{}'))
113 for path, subspec in submodules.iteritems():
114 subspec = submodules[path]
115 ospath = os.path.join(*path.split('/'))
116 wd = os.path.join(self.base, self.root, ospath)
117 self.run_git('svn', 'init', '--prefix=origin/', '-T',
118 subspec['svn_branch'], subspec['svn_url'], cwd=wd)
119 self.run_git('config', '--replace', 'svn-remote.svn.fetch',
120 subspec['svn_branch'] + ':refs/remotes/origin/' +
121 subspec['svn_ref'], cwd=wd)
122 self.run_git('svn', 'fetch', cwd=wd)
123
124
125CHECKOUT_TYPE_MAP = {
126 'gclient': GclientCheckout,
127 'gclient_git_svn': GclientGitSvnCheckout,
128 'git': GitCheckout,
129}
130
131
132def CheckoutFactory(type_name, spec, root):
133 """Factory to build Checkout class instances."""
134 class_ = CHECKOUT_TYPE_MAP.get(type_name)
135 if not class_:
136 raise KeyError('unrecognized checkout type: %s' % type_name)
137 return class_(spec, root)
138
139
140#################################################
141# Utility function and file entry point.
142#################################################
143def usage(msg=None):
144 """Print help and exit."""
145 if msg:
146 print 'Error:', msg
147
148 print (
149"""
150usage: %s <recipe> [--property=value [--property2=value2 ...]]
151""" % os.path.basename(sys.argv[0]))
152 sys.exit(bool(msg))
153
154
155def handle_args(argv):
156 """Gets the recipe name from the command line arguments."""
157 if len(argv) <= 1:
158 usage('Must specify a recipe.')
dpranke@chromium.orge3d147d2013-04-03 20:31:27 +0000159 if argv[1] in ('-h', '--help', 'help'):
160 usage()
agable@chromium.orgcc023502013-04-03 20:24:21 +0000161
162 def looks_like_arg(arg):
163 return arg.startswith('--') and arg.count('=') == 1
164
165 bad_parms = [x for x in argv[2:] if not looks_like_arg(x)]
166 if bad_parms:
167 usage('Got bad arguments %s' % bad_parms)
168
169 recipe = argv[1]
170 props = argv[2:]
171 return recipe, props
172
173
174def run_recipe_fetch(recipe, props, aliased=False):
175 """Invoke a recipe's fetch method with the passed-through args
176 and return its json output as a python object."""
177 recipe_path = os.path.abspath(os.path.join(SCRIPT_PATH, 'recipes', recipe))
178 cmd = [sys.executable, recipe_path + '.py', 'fetch'] + props
179 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
180 spec = json.loads(result)
181 if 'alias' in spec:
182 assert not aliased
183 return run_recipe_fetch(
184 spec['alias']['recipe'], spec['alias']['props'] + props, aliased=True)
185 cmd = [sys.executable, recipe_path + '.py', 'root']
186 result = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
187 root = json.loads(result)
188 return spec, root
189
190
191def run(spec, root):
192 """Perform a checkout with the given type and configuration.
193
194 Args:
195 spec: Checkout configuration returned by the the recipe's fetch_spec
196 method (checkout type, repository url, etc.).
197 root: The directory into which the repo expects to be checkout out.
198 """
199 assert 'type' in spec
200 checkout_type = spec['type']
201 checkout_spec = spec['%s_spec' % checkout_type]
202 try:
203 checkout = CheckoutFactory(checkout_type, checkout_spec, root)
204 except KeyError:
205 return 1
206 if checkout.exists():
207 print 'You appear to already have this checkout.'
208 print 'Aborting to avoid clobbering your work.'
209 return 1
210 checkout.init()
211 return 0
212
213
214def main():
215 recipe, props = handle_args(sys.argv)
216 spec, root = run_recipe_fetch(recipe, props)
217 return run(spec, root)
218
219
220if __name__ == '__main__':
221 sys.exit(main())