Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(67)

Side by Side Diff: scripts/slave/annotated_checkout.py

Issue 15270004: Add step generator protocol, remove annotated_checkout, remove script. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Checkout blobs do not need to be generators Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 #!/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 """Helper script for fully-annotated builds. Performs checkouts of various
7 kinds.
8
9 This script is part of the effort to move all builds to annotator-based systems.
10 Any builder configured to use the AnnotatorFactory uses run.py as its entry
11 point. If that builder's factory_properties include a spec for a checkout, then
12 the work of actually performing that checkout is done here.
13 """
14
15 import cStringIO as StringIO
16 import optparse
17 import os
18 import pipes
19 import subprocess
20 import sys
21
22 from common import annotator
23 from common import chromium_utils
24
25
26 SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
27
28
29 def get_args():
30 """Process command-line arguments."""
31 parser = optparse.OptionParser(
32 description='Checkout helper for annotated builds.')
33 parser.add_option('--type',
34 action='store', type='string', default='',
35 help='type of checkout (i.e. gclient, git, or svn)')
36 parser.add_option('--spec',
37 action='callback', callback=chromium_utils.convert_json,
38 type='string', default={},
39 help='repository spec (url and metadata) to checkout')
40 return parser.parse_args()
41
42
43 class _CheckoutMetaclass(type):
44 """Automatically register Checkout subclasses for factory discoverability."""
45 checkout_registry = {}
46
47 def __new__(mcs, name, bases, attrs):
48 checkout_type = attrs['CHECKOUT_TYPE']
49
50 if checkout_type in mcs.checkout_registry:
51 raise ValueError('Duplicate checkout identifier "%s" found in: %s' %
52 (checkout_type, name))
53
54 # Only the base class is allowed to have no CHECKOUT_TYPE. The base class
55 # should be the only one to specify this metaclass.
56 if not checkout_type and attrs.get('__metaclass__') != mcs:
57 raise ValueError('"%s" CHECKOUT_TYPE cannot be empty or None.' % name)
58
59 newcls = super(_CheckoutMetaclass, mcs).__new__(mcs, name, bases, attrs)
60 # Don't register the base class.
61 if checkout_type:
62 mcs.checkout_registry[checkout_type] = newcls
63 return newcls
64
65
66 class Checkout(object):
67 """Base class for implementing different types of checkouts.
68
69 Attributes:
70 CHECKOUT_TYPE: String identifier used when selecting the type of checkout to
71 perform. All subclasses must specify a unique CHECKOUT_TYPE value.
72 """
73 __metaclass__ = _CheckoutMetaclass
74 CHECKOUT_TYPE = None
75
76 def __init__(self, spec):
77 self.spec = spec
78
79 def setup(self):
80 pass
81
82 def clean(self):
83 pass
84
85 def checkout(self):
86 pass
87
88 def root(self):
89 pass
90
91
92 def CheckoutFactory(type_name, spec):
93 """Factory to build Checkout class instances."""
94 class_ = _CheckoutMetaclass.checkout_registry.get(type_name)
95 if not class_ or not issubclass(class_, Checkout):
96 raise KeyError('unrecognized checkout type: %s' % type_name)
97 return class_(spec)
98
99
100 class GclientCheckout(Checkout):
101 CHECKOUT_TYPE = 'gclient'
102
103 gclient_path = os.path.abspath(
104 os.path.join(SCRIPT_PATH, '..', '..', '..', 'depot_tools', 'gclient'))
105 if sys.platform.startswith('win'):
106 gclient_path += '.bat'
107
108 def __init__(self, *args, **kwargs):
109 super(GclientCheckout, self).__init__(*args, **kwargs)
110 assert 'solutions' in self.spec
111
112 @classmethod
113 def run_gclient(cls, *cmd):
114 print 'Running: gclient %s' % ' '.join(pipes.quote(x) for x in cmd)
115 subprocess.check_call((cls.gclient_path,)+cmd)
116
117 def setup(self):
118 spec_string = ''
119 for key in self.spec:
120 # We should be using json.dumps here, but gclient directly execs the dict
121 # that it receives as the argument to --spec, so we have to have True,
122 # False, and None instead of JSON's true, false, and null.
123 spec_string += '%s = %s\n' % (key, str(self.spec[key]))
124 self.run_gclient('config', '--spec', spec_string)
125
126 def clean(self):
127 self.run_gclient('revert', '--nohooks')
128
129 def checkout(self):
130 self.run_gclient('sync', '--nohooks')
131
132 def root(self):
133 return os.path.abspath(self.spec['solutions'][0]['name'])
134
135
136 class GclientGitCheckout(GclientCheckout):
137 """A gclient checkout tuned for purely git-based DEPS."""
138 CHECKOUT_TYPE = 'gclient_git'
139 def clean(self):
140 # clean() isn't used because the gclient sync flags passed in checkout() do
141 # much the same thing, and they're more correct than doing a separate
142 # 'gclient revert' because it makes sure the other args are correct when a
143 # repo was deleted and needs to be re-cloned (notably --with_branch_heads),
144 # whereas 'revert' uses default args for clone operations.
145 #
146 # TODO(mmoss): To be like current official builders, this step could just
147 # delete the whole <slave_name>/build/ directory and start each build from
148 # scratch. That might be the least bad solution, at least until we have a
149 # reliable gclient method to produce a pristine working dir for git-based
150 # builds (e.g. maybe some combination of 'git reset/clean -fx' and removing
151 # the 'out' directory).
152 pass
153
154 def checkout(self):
155 self.run_gclient('sync', '--verbose', '--with_branch_heads', '--nohooks',
156 '--reset', '--delete_unversioned_trees', '--force')
157
158
159 class GitCheckout(Checkout):
160 """Git specs are a dictionary with up to four keys: |url|, |branch|,
161 |recursive|, and |directory|. Only |url| is required. The others default
162 to empty, which results in using the git-default values of HEAD, False,
163 and the 'humanish' interpretation of the url, respectively. Note that |url|
164 is the full git url of the repo, including username and port number if
165 necessary."""
166 CHECKOUT_TYPE = 'git'
167
168 def __init__(self, *args, **kwargs):
169 super(GitCheckout, self).__init__(*args, **kwargs)
170 assert 'url' in self.spec
171 assert os.pardir not in self.spec.get('directory', '')
172
173 dir_path = self.spec.get('directory')
174 if not dir_path:
175 dir_path = self.spec['url'].rsplit('/', 1)[-1]
176 if dir_path.endswith('.git'): # ex: https://host/foobar.git
177 dir_path = dir_path[:-len('.git')]
178 if not dir_path: # ex: ssh://host:repo/foobar/.git
179 dir_path = dir_path.rsplit('/', 1)[-1]
180 self.cwd = os.path.abspath(os.path.join(os.curdir, dir_path))
181
182 def setup(self):
183 if not os.path.exists(self.cwd):
184 os.makedirs(self.cwd)
185
186 try:
187 self.run_git('branch')
188 exists = True
189 except subprocess.CalledProcessError:
190 exists = False
191 if exists:
192 self.run_git('remote', 'rm', 'origin')
193 else:
194 self.run_git('init')
195 self.run_git('remote', 'add', 'origin', self.spec['url'])
196 # TODO(agable): add support for git crup.
197 if self.spec.get('recursive'):
198 self.run_git('fetch', 'origin', '--recurse-submodules')
199 else:
200 self.run_git('fetch', 'origin')
201 branch = self.spec.get('branch', 'master')
202 self.run_git('update-ref', 'refs/heads/%s' % branch, 'origin/%s' % branch)
203
204 def run_git(self, *cmd):
205 cmd = ('git',) + cmd
206 print 'Running: %s' % (' '.join(pipes.quote(x) for x in cmd))
207 subprocess.check_call(cmd, cwd=self.cwd)
208
209 def clean(self):
210 self.run_git('clean', '-f', '-d', '-x')
211
212 def checkout(self):
213 self.run_git('checkout', '-f', self.spec.get('branch', 'master'))
214 self.run_git('submodule', 'update', '--init', '--recursive')
215
216 def root(self):
217 return self.cwd
218
219
220 class SvnCheckout(Checkout):
221 CHECKOUT_TYPE = 'svn'
222
223
224 def run(checkout_type, checkout_spec, test_mode=False):
225 """Perform a checkout with the given type and configuration.
226
227 Args:
228 checkout_type: Type of checkout to perform (matching a Checkout subclass
229 CHECKOUT_TYPE attribute).
230 checkout_spec: Configuration values needed for the type of checkout
231 (repository url, etc.).
232 test_mode: If we're in test_mode, just return error code and root without
233 actually doing anything.
234
235 Returns:
236 Tuple of (<retcode>, <root_path>) where root_path is the absolute path
237 to the 'root' of the checkout (as defined by |checkout_type|).
238 """
239 stream = sys.stdout
240 if test_mode:
241 stream = StringIO.StringIO()
242 stream = annotator.StructuredAnnotationStream(
243 seed_steps=['checkout_setup', 'checkout_clean', 'checkout'],
244 stream=stream)
245
246 with stream.step('checkout_setup') as s:
247 try:
248 checkout = CheckoutFactory(checkout_type, checkout_spec)
249 except KeyError as e:
250 s.step_text(e)
251 s.step_failure()
252 return (1, None)
253 if test_mode:
254 return (0, checkout.root())
255 checkout.setup()
256 with stream.step('checkout_clean') as s:
257 checkout.clean()
258 with stream.step('checkout') as s:
259 checkout.checkout()
260 return (0, checkout.root())
261
262
263 def main():
264 opts, _ = get_args()
265 return run(opts.type, opts.spec)[0]
266
267
268 if __name__ == '__main__':
269 sys.exit(main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698