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

Side by Side Diff: recipe_modules/gclient/api.py

Issue 1651033004: depot_tools: import bot_update gclient git rietveld tryserver recipe modules (reland #1) (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: 80cols Created 4 years, 10 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
« no previous file with comments | « recipe_modules/gclient/__init__.py ('k') | recipe_modules/gclient/config.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 from recipe_engine import recipe_api
6
7
8 class RevisionResolver(object):
9 """Resolves the revision based on build properties."""
10
11 def resolve(self, properties): # pragma: no cover
12 raise NotImplementedError()
13
14
15 class RevisionFallbackChain(RevisionResolver):
16 """Specify that a given project's sync revision follows the fallback chain."""
17 def __init__(self, default=None):
18 self._default = default
19
20 def resolve(self, properties):
21 """Resolve the revision via the revision fallback chain.
22
23 If the given revision was set using the revision_fallback_chain() function,
24 this function will follow the chain, looking at relevant build properties
25 until it finds one set or reaches the end of the chain and returns the
26 default. If the given revision was not set using revision_fallback_chain(),
27 this function just returns it as-is.
28 """
29 return (properties.get('parent_got_revision') or
30 properties.get('orig_revision') or
31 properties.get('revision') or
32 self._default)
33
34
35 class ProjectRevisionResolver(RevisionResolver):
36 """Revision resolver that takes into account the project."""
37 def __init__(self, project, parent_got_revision=None):
38 self.project = project
39 self.parent_got_revision = parent_got_revision or 'parent_got_revision'
40
41 # TODO(phajdan.jr): Move to proper repo and add coverage.
42 def resolve(self, properties): # pragma: no cover
43 """Resolve the revision if project matches, otherwise default to HEAD."""
44 if properties.get('project') == self.project:
45 return (properties.get(self.parent_got_revision) or
46 properties.get('revision') or
47 'HEAD')
48 return (properties.get(self.parent_got_revision) or
49 'HEAD')
50
51
52 def jsonish_to_python(spec, is_top=False):
53 ret = ''
54 if is_top: # We're the 'top' level, so treat this dict as a suite.
55 ret = '\n'.join(
56 '%s = %s' % (k, jsonish_to_python(spec[k])) for k in sorted(spec)
57 )
58 else:
59 if isinstance(spec, dict):
60 ret += '{'
61 ret += ', '.join(
62 "%s: %s" % (repr(str(k)), jsonish_to_python(spec[k]))
63 for k in sorted(spec)
64 )
65 ret += '}'
66 elif isinstance(spec, list):
67 ret += '['
68 ret += ', '.join(jsonish_to_python(x) for x in spec)
69 ret += ']'
70 elif isinstance(spec, basestring):
71 ret = repr(str(spec))
72 else:
73 ret = repr(spec)
74 return ret
75
76 class GclientApi(recipe_api.RecipeApi):
77 # Singleton object to indicate to checkout() that we should run a revert if
78 # we detect that we're on the tryserver.
79 RevertOnTryserver = object()
80
81 def __init__(self, **kwargs):
82 super(GclientApi, self).__init__(**kwargs)
83 self.USE_MIRROR = None
84 self._spec_alias = None
85
86 def __call__(self, name, cmd, infra_step=True, **kwargs):
87 """Wrapper for easy calling of gclient steps."""
88 assert isinstance(cmd, (list, tuple))
89 prefix = 'gclient '
90 if self.spec_alias:
91 prefix = ('[spec: %s] ' % self.spec_alias) + prefix
92
93 return self.m.python(prefix + name,
94 self.m.path['depot_tools'].join('gclient.py'),
95 cmd,
96 infra_step=infra_step,
97 **kwargs)
98
99 @property
100 def use_mirror(self):
101 """Indicates if gclient will use mirrors in its configuration."""
102 if self.USE_MIRROR is None:
103 self.USE_MIRROR = self.m.properties.get('use_mirror', True)
104 return self.USE_MIRROR
105
106 @use_mirror.setter
107 def use_mirror(self, val): # pragma: no cover
108 self.USE_MIRROR = val
109
110 @property
111 def spec_alias(self):
112 """Optional name for the current spec for step naming."""
113 return self._spec_alias
114
115 @spec_alias.setter
116 def spec_alias(self, name):
117 self._spec_alias = name
118
119 @spec_alias.deleter
120 def spec_alias(self):
121 self._spec_alias = None
122
123 def get_config_defaults(self):
124 ret = {
125 'USE_MIRROR': self.use_mirror
126 }
127 ret['CACHE_DIR'] = self.m.path['root'].join('git_cache')
128 return ret
129
130 def resolve_revision(self, revision):
131 if hasattr(revision, 'resolve'):
132 return revision.resolve(self.m.properties)
133 return revision
134
135 def sync(self, cfg, with_branch_heads=False, **kwargs):
136 revisions = []
137 for i, s in enumerate(cfg.solutions):
138 if s.safesync_url: # prefer safesync_url in gclient mode
139 continue
140 if i == 0 and s.revision is None:
141 s.revision = RevisionFallbackChain()
142
143 if s.revision is not None and s.revision != '':
144 fixed_revision = self.resolve_revision(s.revision)
145 if fixed_revision:
146 revisions.extend(['--revision', '%s@%s' % (s.name, fixed_revision)])
147
148 for name, revision in sorted(cfg.revisions.items()):
149 fixed_revision = self.resolve_revision(revision)
150 if fixed_revision:
151 revisions.extend(['--revision', '%s@%s' % (name, fixed_revision)])
152
153 test_data_paths = set(cfg.got_revision_mapping.keys() +
154 [s.name for s in cfg.solutions])
155 step_test_data = lambda: (
156 self.test_api.output_json(test_data_paths, cfg.GIT_MODE))
157 try:
158 if not cfg.GIT_MODE:
159 args = ['sync', '--nohooks', '--force', '--verbose']
160 if cfg.delete_unversioned_trees:
161 args.append('--delete_unversioned_trees')
162 if with_branch_heads:
163 args.append('--with_branch_heads')
164 self('sync', args + revisions + ['--output-json', self.m.json.output()],
165 step_test_data=step_test_data,
166 **kwargs)
167 else:
168 # clean() isn't used because the gclient sync flags passed in checkout()
169 # do much the same thing, and they're more correct than doing a separate
170 # 'gclient revert' because it makes sure the other args are correct when
171 # a repo was deleted and needs to be re-cloned (notably
172 # --with_branch_heads), whereas 'revert' uses default args for clone
173 # operations.
174 #
175 # TODO(mmoss): To be like current official builders, this step could
176 # just delete the whole <slave_name>/build/ directory and start each
177 # build from scratch. That might be the least bad solution, at least
178 # until we have a reliable gclient method to produce a pristine working
179 # dir for git-based builds (e.g. maybe some combination of 'git
180 # reset/clean -fx' and removing the 'out' directory).
181 j = '-j2' if self.m.platform.is_win else '-j8'
182 args = ['sync', '--verbose', '--with_branch_heads', '--nohooks', j,
183 '--reset', '--force', '--upstream', '--no-nag-max']
184 if cfg.delete_unversioned_trees:
185 args.append('--delete_unversioned_trees')
186 self('sync', args + revisions +
187 ['--output-json', self.m.json.output()],
188 step_test_data=step_test_data,
189 **kwargs)
190 finally:
191 result = self.m.step.active_result
192 data = result.json.output
193 for path, info in data['solutions'].iteritems():
194 # gclient json paths always end with a slash
195 path = path.rstrip('/')
196 if path in cfg.got_revision_mapping:
197 propname = cfg.got_revision_mapping[path]
198 result.presentation.properties[propname] = info['revision']
199
200 return result
201
202 def inject_parent_got_revision(self, gclient_config=None, override=False):
203 """Match gclient config to build revisions obtained from build_properties.
204
205 Args:
206 gclient_config (gclient config object) - The config to manipulate. A value
207 of None manipulates the module's built-in config (self.c).
208 override (bool) - If True, will forcibly set revision and custom_vars
209 even if the config already contains values for them.
210 """
211 cfg = gclient_config or self.c
212
213 for prop, custom_var in cfg.parent_got_revision_mapping.iteritems():
214 val = str(self.m.properties.get(prop, ''))
215 # TODO(infra): Fix coverage.
216 if val: # pragma: no cover
217 # Special case for 'src', inject into solutions[0]
218 if custom_var is None:
219 # This is not covered because we are deprecating this feature and
220 # it is no longer used by the public recipes.
221 if cfg.solutions[0].revision is None or override: # pragma: no cover
222 cfg.solutions[0].revision = val
223 else:
224 if custom_var not in cfg.solutions[0].custom_vars or override:
225 cfg.solutions[0].custom_vars[custom_var] = val
226
227 def checkout(self, gclient_config=None, revert=RevertOnTryserver,
228 inject_parent_got_revision=True, with_branch_heads=False,
229 **kwargs):
230 """Return a step generator function for gclient checkouts."""
231 cfg = gclient_config or self.c
232 assert cfg.complete()
233
234 if revert is self.RevertOnTryserver:
235 revert = self.m.tryserver.is_tryserver
236
237 if inject_parent_got_revision:
238 self.inject_parent_got_revision(cfg, override=True)
239
240 spec_string = jsonish_to_python(cfg.as_jsonish(), True)
241
242 self('setup', ['config', '--spec', spec_string], **kwargs)
243
244 sync_step = None
245 try:
246 if not cfg.GIT_MODE:
247 try:
248 if revert:
249 self.revert(**kwargs)
250 finally:
251 sync_step = self.sync(cfg, with_branch_heads=with_branch_heads,
252 **kwargs)
253 else:
254 sync_step = self.sync(cfg, with_branch_heads=with_branch_heads,
255 **kwargs)
256
257 cfg_cmds = [
258 ('user.name', 'local_bot'),
259 ('user.email', 'local_bot@example.com'),
260 ]
261 for var, val in cfg_cmds:
262 name = 'recurse (git config %s)' % var
263 self(name, ['recurse', 'git', 'config', var, val], **kwargs)
264
265 finally:
266 cwd = kwargs.get('cwd', self.m.path['slave_build'])
267 if 'checkout' not in self.m.path:
268 self.m.path['checkout'] = cwd.join(
269 *cfg.solutions[0].name.split(self.m.path.sep))
270
271 return sync_step
272
273 def revert(self, **kwargs):
274 """Return a gclient_safe_revert step."""
275 # Not directly calling gclient, so don't use self().
276 alias = self.spec_alias
277 prefix = '%sgclient ' % (('[spec: %s] ' % alias) if alias else '')
278
279 return self.m.python(prefix + 'revert',
280 self.m.path['build'].join('scripts', 'slave', 'gclient_safe_revert.py'),
281 ['.', self.m.path['depot_tools'].join('gclient',
282 platform_ext={'win': '.bat'})],
283 infra_step=True,
284 **kwargs
285 )
286
287 def runhooks(self, args=None, name='runhooks', **kwargs):
288 args = args or []
289 assert isinstance(args, (list, tuple))
290 return self(
291 name, ['runhooks'] + list(args), infra_step=False, **kwargs)
292
293 @property
294 def is_blink_mode(self):
295 """ Indicates wether the caller is to use the Blink config rather than the
296 Chromium config. This may happen for one of two reasons:
297 1. The builder is configured to always use TOT Blink. (factory property
298 top_of_tree_blink=True)
299 2. A try job comes in that applies to the Blink tree. (patch_project is
300 blink)
301 """
302 return (
303 self.m.properties.get('top_of_tree_blink') or
304 self.m.properties.get('patch_project') == 'blink')
305
306 def break_locks(self):
307 """Remove all index.lock files. If a previous run of git crashed, bot was
308 reset, etc... we might end up with leftover index.lock files.
309 """
310 self.m.python.inline(
311 'cleanup index.lock',
312 """
313 import os, sys
314
315 build_path = sys.argv[1]
316 if os.path.exists(build_path):
317 for (path, dir, files) in os.walk(build_path):
318 for cur_file in files:
319 if cur_file.endswith('index.lock'):
320 path_to_file = os.path.join(path, cur_file)
321 print 'deleting %s' % path_to_file
322 os.remove(path_to_file)
323 """,
324 args=[self.m.path['slave_build']],
325 infra_step=True,
326 )
OLDNEW
« no previous file with comments | « recipe_modules/gclient/__init__.py ('k') | recipe_modules/gclient/config.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698