blob: ff79950d8d67824bcb6ec1598aaf367ad67edbf1 [file] [log] [blame]
[email protected]fb2b8eb2009-04-23 21:03:421#!/usr/bin/python
[email protected]ba551772010-02-03 18:21:422# Copyright (c) 2010 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.
[email protected]fb2b8eb2009-04-23 21:03:425
[email protected]0b6a0842010-06-15 14:34:196"""Meta checkout manager supporting both Subversion and GIT.
[email protected]fb2b8eb2009-04-23 21:03:427
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
[email protected]67820ef2009-07-27 17:23:0024 working copy as a result of a "sync"/"update" or "revert" operation. This
[email protected]0b6a0842010-06-15 14:34:1925 can be prevented by using --nohooks (hooks run by default). Hooks can also
[email protected]5df6a462009-08-28 18:52:2626 be forced to run with the "runhooks" operation. If "sync" is run with
[email protected]fb2b8eb2009-04-23 21:03:4227 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
[email protected]71b40682009-07-31 23:40:0941 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
[email protected]fb2b8eb2009-04-23 21:03:4244
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
[email protected]df2b3152010-07-21 17:35:2452__version__ = "0.5"
[email protected]fb2b8eb2009-04-23 21:03:4253
[email protected]754960e2009-09-21 12:31:0554import logging
[email protected]fb2b8eb2009-04-23 21:03:4255import optparse
56import os
[email protected]2e38de72009-09-28 17:04:4757import pprint
[email protected]fb2b8eb2009-04-23 21:03:4258import re
[email protected]4b90e3a2010-07-01 20:28:2659import subprocess
[email protected]fb2b8eb2009-04-23 21:03:4260import sys
[email protected]fb2b8eb2009-04-23 21:03:4261import urlparse
[email protected]fb2b8eb2009-04-23 21:03:4262import urllib
63
[email protected]ada4c652009-12-03 15:32:0164import breakpad
65
[email protected]5f3eee32009-09-17 00:34:3066import gclient_scm
67import gclient_utils
[email protected]1f7a3d12010-02-04 15:11:5068from third_party.repo.progress import Progress
[email protected]fb2b8eb2009-04-23 21:03:4269
[email protected]fb2b8eb2009-04-23 21:03:4270
[email protected]1f7d1182010-05-17 18:17:3871def attr(attr, data):
72 """Sets an attribute on a function."""
73 def hook(fn):
74 setattr(fn, attr, data)
75 return fn
76 return hook
[email protected]e3da35f2010-03-09 21:40:4577
[email protected]fb2b8eb2009-04-23 21:03:4278
[email protected]fb2b8eb2009-04-23 21:03:4279## GClient implementation.
80
81
[email protected]116704f2010-06-11 17:34:3882class GClientKeywords(object):
83 class FromImpl(object):
84 """Used to implement the From() syntax."""
85
86 def __init__(self, module_name, sub_target_name=None):
87 """module_name is the dep module we want to include from. It can also be
88 the name of a subdirectory to include from.
89
90 sub_target_name is an optional parameter if the module name in the other
91 DEPS file is different. E.g., you might want to map src/net to net."""
92 self.module_name = module_name
93 self.sub_target_name = sub_target_name
94
95 def __str__(self):
96 return 'From(%s, %s)' % (repr(self.module_name),
97 repr(self.sub_target_name))
98
[email protected]116704f2010-06-11 17:34:3899 class FileImpl(object):
100 """Used to implement the File('') syntax which lets you sync a single file
[email protected]e3216c62010-07-08 03:31:43101 from a SVN repo."""
[email protected]116704f2010-06-11 17:34:38102
103 def __init__(self, file_location):
104 self.file_location = file_location
105
106 def __str__(self):
107 return 'File("%s")' % self.file_location
108
109 def GetPath(self):
110 return os.path.split(self.file_location)[0]
111
112 def GetFilename(self):
113 rev_tokens = self.file_location.split('@')
114 return os.path.split(rev_tokens[0])[1]
115
116 def GetRevision(self):
117 rev_tokens = self.file_location.split('@')
118 if len(rev_tokens) > 1:
119 return rev_tokens[1]
120 return None
121
122 class VarImpl(object):
123 def __init__(self, custom_vars, local_scope):
124 self._custom_vars = custom_vars
125 self._local_scope = local_scope
126
127 def Lookup(self, var_name):
128 """Implements the Var syntax."""
129 if var_name in self._custom_vars:
130 return self._custom_vars[var_name]
131 elif var_name in self._local_scope.get("vars", {}):
132 return self._local_scope["vars"][var_name]
133 raise gclient_utils.Error("Var is not defined: %s" % var_name)
134
135
[email protected]54a07a22010-06-14 19:07:39136class Dependency(GClientKeywords):
137 """Object that represents a dependency checkout."""
[email protected]9eda4112010-06-11 18:56:10138 DEPS_FILE = 'DEPS'
[email protected]0b6a0842010-06-15 14:34:19139
[email protected]54a07a22010-06-14 19:07:39140 def __init__(self, parent, name, url, safesync_url=None, custom_deps=None,
[email protected]0b6a0842010-06-15 14:34:19141 custom_vars=None, deps_file=None):
[email protected]54a07a22010-06-14 19:07:39142 GClientKeywords.__init__(self)
143 self.parent = parent
144 self.name = name
145 self.url = url
[email protected]df2b3152010-07-21 17:35:24146 self.parsed_url = None
[email protected]54a07a22010-06-14 19:07:39147 # These 2 are only set in .gclient and not in DEPS files.
148 self.safesync_url = safesync_url
149 self.custom_vars = custom_vars or {}
150 self.custom_deps = custom_deps or {}
[email protected]0b6a0842010-06-15 14:34:19151 self.deps_hooks = []
[email protected]54a07a22010-06-14 19:07:39152 self.dependencies = []
153 self.deps_file = deps_file or self.DEPS_FILE
[email protected]df2b3152010-07-21 17:35:24154 # A cache of the files affected by the current operation, necessary for
155 # hooks.
156 self.file_list = []
[email protected]85c2a192010-07-22 21:14:43157 # If it is not set to True, the dependency wasn't processed for its child
158 # dependency, i.e. its DEPS wasn't read.
[email protected]271375b2010-06-23 19:17:38159 self.deps_parsed = False
[email protected]85c2a192010-07-22 21:14:43160 # A direct reference is dependency that is referenced by a deps, deps_os or
161 # solution. A indirect one is one that was loaded with From() or that
162 # exceeded recursion limit.
[email protected]271375b2010-06-23 19:17:38163 self.direct_reference = False
[email protected]fb2b8eb2009-04-23 21:03:42164
[email protected]54a07a22010-06-14 19:07:39165 # Sanity checks
166 if not self.name and self.parent:
167 raise gclient_utils.Error('Dependency without name')
[email protected]bffb9042010-07-22 20:59:36168 tree = dict((d.name, d) for d in self.tree(False))
169 if self.name in tree:
170 raise gclient_utils.Error(
171 'Dependency %s specified more than once:\n %s\nvs\n %s' %
172 (self.name, tree[self.name].hierarchy(), self.hierarchy()))
[email protected]54a07a22010-06-14 19:07:39173 if not isinstance(self.url,
174 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
175 raise gclient_utils.Error('dependency url must be either a string, None, '
176 'File() or From() instead of %s' %
177 self.url.__class__.__name__)
178 if '/' in self.deps_file or '\\' in self.deps_file:
179 raise gclient_utils.Error('deps_file name must not be a path, just a '
180 'filename. %s' % self.deps_file)
181
[email protected]df2b3152010-07-21 17:35:24182 def LateOverride(self, url):
183 overriden_url = self.get_custom_deps(self.name, url)
184 if overriden_url != url:
185 self.parsed_url = overriden_url
186 logging.debug('%s, %s was overriden to %s' % (self.name, url,
187 self.parsed_url))
188 elif isinstance(url, self.FromImpl):
189 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
190 if not len(ref) == 1:
191 raise Exception('Failed to find one reference to %s. %s' % (
192 url.module_name, ref))
193 ref = ref[0]
[email protected]98d05fa2010-07-22 21:58:01194 sub_target = url.sub_target_name or self.name
[email protected]df2b3152010-07-21 17:35:24195 # Make sure the referenced dependency DEPS file is loaded and file the
196 # inner referenced dependency.
197 ref.ParseDepsFile(False)
198 found_dep = None
199 for d in ref.dependencies:
200 if d.name == sub_target:
201 found_dep = d
202 break
203 if not found_dep:
204 raise Exception('Couldn\'t find %s in %s, referenced by %s' % (
205 sub_target, ref.name, self.name))
206 # Call LateOverride() again.
207 self.parsed_url = found_dep.LateOverride(found_dep.url)
208 logging.debug('%s, %s to %s' % (self.name, url, self.parsed_url))
209 elif isinstance(url, basestring):
210 parsed_url = urlparse.urlparse(url)
211 if not parsed_url[0]:
212 # A relative url. Fetch the real base.
213 path = parsed_url[2]
214 if not path.startswith('/'):
215 raise gclient_utils.Error(
216 'relative DEPS entry \'%s\' must begin with a slash' % url)
217 # Create a scm just to query the full url.
218 parent_url = self.parent.parsed_url
219 if isinstance(parent_url, self.FileImpl):
220 parent_url = parent_url.file_location
221 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
222 self.parsed_url = scm.FullUrlForRelativeUrl(url)
223 else:
224 self.parsed_url = url
225 logging.debug('%s, %s -> %s' % (self.name, url, self.parsed_url))
226 elif isinstance(url, self.FileImpl):
227 self.parsed_url = url
228 logging.debug('%s, %s -> %s (File)' % (self.name, url, self.parsed_url))
229 return self.parsed_url
230
[email protected]271375b2010-06-23 19:17:38231 def ParseDepsFile(self, direct_reference):
232 """Parses the DEPS file for this dependency."""
233 if direct_reference:
234 # Maybe it was referenced earlier by a From() keyword but it's now
235 # directly referenced.
236 self.direct_reference = direct_reference
[email protected]df2b3152010-07-21 17:35:24237 if self.deps_parsed:
238 return
[email protected]271375b2010-06-23 19:17:38239 self.deps_parsed = True
240 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
241 if not os.path.isfile(filepath):
[email protected]df2b3152010-07-21 17:35:24242 return
[email protected]271375b2010-06-23 19:17:38243 deps_content = gclient_utils.FileRead(filepath)
[email protected]0d425922010-06-21 19:22:24244
[email protected]271375b2010-06-23 19:17:38245 # Eval the content.
246 # One thing is unintuitive, vars= {} must happen before Var() use.
247 local_scope = {}
248 var = self.VarImpl(self.custom_vars, local_scope)
249 global_scope = {
250 'File': self.FileImpl,
251 'From': self.FromImpl,
252 'Var': var.Lookup,
253 'deps_os': {},
254 }
[email protected]5990f9d2010-07-07 18:02:58255 try:
256 exec(deps_content, global_scope, local_scope)
257 except SyntaxError, e:
258 gclient_utils.SyntaxErrorToError(filepath, e)
[email protected]271375b2010-06-23 19:17:38259 deps = local_scope.get('deps', {})
[email protected]fb2b8eb2009-04-23 21:03:42260 # load os specific dependencies if defined. these dependencies may
261 # override or extend the values defined by the 'deps' member.
[email protected]271375b2010-06-23 19:17:38262 if 'deps_os' in local_scope:
263 for deps_os_key in self.enforced_os():
264 os_deps = local_scope['deps_os'].get(deps_os_key, {})
265 if len(self.enforced_os()) > 1:
266 # Ignore any conflict when including deps for more than one
[email protected]fb2b8eb2009-04-23 21:03:42267 # platform, so we collect the broadest set of dependencies available.
268 # We may end up with the wrong revision of something for our
269 # platform, but this is the best we can do.
270 deps.update([x for x in os_deps.items() if not x[0] in deps])
271 else:
272 deps.update(os_deps)
273
[email protected]271375b2010-06-23 19:17:38274 self.deps_hooks.extend(local_scope.get('hooks', []))
275
276 # If a line is in custom_deps, but not in the solution, we want to append
277 # this line to the solution.
278 for d in self.custom_deps:
279 if d not in deps:
280 deps[d] = self.custom_deps[d]
[email protected]fb2b8eb2009-04-23 21:03:42281
282 # If use_relative_paths is set in the DEPS file, regenerate
283 # the dictionary using paths relative to the directory containing
284 # the DEPS file.
[email protected]271375b2010-06-23 19:17:38285 use_relative_paths = local_scope.get('use_relative_paths', False)
286 if use_relative_paths:
[email protected]fb2b8eb2009-04-23 21:03:42287 rel_deps = {}
288 for d, url in deps.items():
289 # normpath is required to allow DEPS to use .. in their
290 # dependency local path.
[email protected]271375b2010-06-23 19:17:38291 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
292 deps = rel_deps
[email protected]fb2b8eb2009-04-23 21:03:42293
[email protected]df2b3152010-07-21 17:35:24294 # Convert the deps into real Dependency.
295 for name, url in deps.iteritems():
296 if name in [s.name for s in self.dependencies]:
297 raise
298 self.dependencies.append(Dependency(self, name, url))
[email protected]85c2a192010-07-22 21:14:43299 # Note: do not sort by name, the dependencies must be specified in the
300 # logical order.
[email protected]df2b3152010-07-21 17:35:24301 logging.info('Loaded: %s' % str(self))
[email protected]fb2b8eb2009-04-23 21:03:42302
[email protected]df2b3152010-07-21 17:35:24303 def RunCommandRecursively(self, options, revision_overrides,
304 command, args, pm):
305 """Runs 'command' before parsing the DEPS in case it's a initial checkout
306 or a revert."""
307 assert self.file_list == []
308 # When running runhooks, there's no need to consult the SCM.
309 # All known hooks are expected to run unconditionally regardless of working
310 # copy state, so skip the SCM status check.
311 run_scm = command not in ('runhooks', None)
312 self.LateOverride(self.url)
313 if run_scm and self.parsed_url:
314 if isinstance(self.parsed_url, self.FileImpl):
315 # Special support for single-file checkout.
316 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
317 options.revision = self.parsed_url.GetRevision()
318 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
319 self.root_dir(),
320 self.name)
321 scm.RunCommand('updatesingle', options,
322 args + [self.parsed_url.GetFilename()],
323 self.file_list)
324 else:
325 options.revision = revision_overrides.get(self.name)
326 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
327 scm.RunCommand(command, options, args, self.file_list)
328 self.file_list = [os.path.join(self.name, f.strip())
329 for f in self.file_list]
330 options.revision = None
331 if pm:
332 # The + 1 comes from the fact that .gclient is considered a step in
333 # itself, .i.e. this code is called one time for the .gclient. This is not
334 # conceptually correct but it simplifies code.
335 pm._total = len(self.tree(False)) + 1
336 pm.update()
337 if self.recursion_limit():
338 # Then we can parse the DEPS file.
339 self.ParseDepsFile(True)
340 if pm:
341 pm._total = len(self.tree(False)) + 1
342 pm.update(0)
343 # Parse the dependencies of this dependency.
344 for s in self.dependencies:
345 # TODO(maruel): All these can run concurrently! No need for threads,
346 # just buffer stdout&stderr on pipes and flush as they complete.
347 # Watch out for stdin.
348 s.RunCommandRecursively(options, revision_overrides, command, args, pm)
[email protected]fb2b8eb2009-04-23 21:03:42349
[email protected]df2b3152010-07-21 17:35:24350 def RunHooksRecursively(self, options):
351 """Evaluates all hooks, running actions as needed. RunCommandRecursively()
352 must have been called before to load the DEPS."""
[email protected]dc7445d2010-07-09 21:05:29353 # If "--force" was specified, run all hooks regardless of what files have
[email protected]df2b3152010-07-21 17:35:24354 # changed.
355 if self.deps_hooks and self.direct_reference:
356 # TODO(maruel): If the user is using git or git-svn, then we don't know
357 # what files have changed so we always run all hooks. It'd be nice to fix
358 # that.
359 if (options.force or
360 isinstance(self.parsed_url, self.FileImpl) or
361 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
362 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
363 for hook_dict in self.deps_hooks:
364 self._RunHookAction(hook_dict, [])
365 else:
366 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
367 # Convert all absolute paths to relative.
368 for i in range(len(self.file_list)):
369 # It depends on the command being executed (like runhooks vs sync).
370 if not os.path.isabs(self.file_list[i]):
371 continue
[email protected]dc7445d2010-07-09 21:05:29372
[email protected]df2b3152010-07-21 17:35:24373 prefix = os.path.commonprefix([self.root_dir().lower(),
374 self.file_list[i].lower()])
375 self.file_list[i] = self.file_list[i][len(prefix):]
376
377 # Strip any leading path separators.
378 while (self.file_list[i].startswith('\\') or
379 self.file_list[i].startswith('/')):
380 self.file_list[i] = self.file_list[i][1:]
381
382 # Run hooks on the basis of whether the files from the gclient operation
383 # match each hook's pattern.
384 for hook_dict in self.deps_hooks:
385 pattern = re.compile(hook_dict['pattern'])
386 matching_file_list = [f for f in self.file_list if pattern.search(f)]
387 if matching_file_list:
388 self._RunHookAction(hook_dict, matching_file_list)
389 if self.recursion_limit():
390 for s in self.dependencies:
391 s.RunHooksRecursively(options)
[email protected]fb2b8eb2009-04-23 21:03:42392
[email protected]eaf61062010-07-07 18:42:39393 def _RunHookAction(self, hook_dict, matching_file_list):
394 """Runs the action from a single hook."""
395 logging.info(hook_dict)
396 logging.info(matching_file_list)
397 command = hook_dict['action'][:]
398 if command[0] == 'python':
399 # If the hook specified "python" as the first item, the action is a
400 # Python script. Run it by starting a new copy of the same
401 # interpreter.
402 command[0] = sys.executable
403
404 if '$matching_files' in command:
405 splice_index = command.index('$matching_files')
406 command[splice_index:splice_index + 1] = matching_file_list
407
408 # Use a discrete exit status code of 2 to indicate that a hook action
409 # failed. Users of this script may wish to treat hook action failures
410 # differently from VC failures.
411 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
412
[email protected]271375b2010-06-23 19:17:38413 def root_dir(self):
414 return self.parent.root_dir()
415
416 def enforced_os(self):
417 return self.parent.enforced_os()
418
[email protected]d36fba82010-06-28 16:50:40419 def recursion_limit(self):
420 return self.parent.recursion_limit() - 1
421
422 def tree(self, force_all):
423 return self.parent.tree(force_all)
424
[email protected]c57e4f22010-07-22 21:37:46425 def subtree(self, force_all):
426 result = []
427 # Add breadth-first.
428 if self.direct_reference or force_all:
429 for d in self.dependencies:
[email protected]044f4e32010-07-22 21:59:57430 result.append(d)
[email protected]c57e4f22010-07-22 21:37:46431 for d in self.dependencies:
432 result.extend(d.subtree(force_all))
433 return result
434
[email protected]d36fba82010-06-28 16:50:40435 def get_custom_deps(self, name, url):
436 """Returns a custom deps if applicable."""
437 if self.parent:
438 url = self.parent.get_custom_deps(name, url)
439 # None is a valid return value to disable a dependency.
440 return self.custom_deps.get(name, url)
441
442 def __str__(self):
443 out = []
444 for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars',
[email protected]df2b3152010-07-21 17:35:24445 'deps_hooks', 'file_list'):
[email protected]d36fba82010-06-28 16:50:40446 # 'deps_file'
447 if self.__dict__[i]:
448 out.append('%s: %s' % (i, self.__dict__[i]))
449
450 for d in self.dependencies:
451 out.extend([' ' + x for x in str(d).splitlines()])
452 out.append('')
453 return '\n'.join(out)
454
455 def __repr__(self):
456 return '%s: %s' % (self.name, self.url)
457
[email protected]bffb9042010-07-22 20:59:36458 def hierarchy(self):
[email protected]bc2d2f92010-07-22 21:26:48459 """Returns a human-readable hierarchical reference to a Dependency."""
[email protected]bffb9042010-07-22 20:59:36460 out = '%s(%s)' % (self.name, self.url)
461 i = self.parent
462 while i and i.name:
463 out = '%s(%s) -> %s' % (i.name, i.url, out)
464 i = i.parent
465 return out
466
[email protected]9a66ddf2010-06-16 16:54:16467
468class GClient(Dependency):
[email protected]d36fba82010-06-28 16:50:40469 """Object that represent a gclient checkout. A tree of Dependency(), one per
470 solution or DEPS entry."""
[email protected]9a66ddf2010-06-16 16:54:16471
472 DEPS_OS_CHOICES = {
473 "win32": "win",
474 "win": "win",
475 "cygwin": "win",
476 "darwin": "mac",
477 "mac": "mac",
478 "unix": "unix",
479 "linux": "unix",
480 "linux2": "unix",
481 }
482
483 DEFAULT_CLIENT_FILE_TEXT = ("""\
484solutions = [
485 { "name" : "%(solution_name)s",
486 "url" : "%(solution_url)s",
487 "custom_deps" : {
488 },
[email protected]73e21142010-07-05 13:32:01489 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16490 },
491]
492""")
493
494 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
495 { "name" : "%(solution_name)s",
496 "url" : "%(solution_url)s",
497 "custom_deps" : {
[email protected]73e21142010-07-05 13:32:01498%(solution_deps)s },
499 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16500 },
501""")
502
503 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
504# Snapshot generated with gclient revinfo --snapshot
505solutions = [
[email protected]73e21142010-07-05 13:32:01506%(solution_list)s]
[email protected]9a66ddf2010-06-16 16:54:16507""")
508
509 def __init__(self, root_dir, options):
510 Dependency.__init__(self, None, None, None)
[email protected]0d425922010-06-21 19:22:24511 self._options = options
[email protected]271375b2010-06-23 19:17:38512 if options.deps_os:
513 enforced_os = options.deps_os.split(',')
514 else:
515 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
516 if 'all' in enforced_os:
517 enforced_os = self.DEPS_OS_CHOICES.itervalues()
518 self._enforced_os = list(set(enforced_os))
519 self._root_dir = root_dir
[email protected]9a66ddf2010-06-16 16:54:16520 self.config_content = None
[email protected]d36fba82010-06-28 16:50:40521 # Do not change previous behavior. Only solution level and immediate DEPS
522 # are processed.
523 self._recursion_limit = 2
[email protected]9a66ddf2010-06-16 16:54:16524
525 def SetConfig(self, content):
526 assert self.dependencies == []
527 config_dict = {}
528 self.config_content = content
529 try:
530 exec(content, config_dict)
531 except SyntaxError, e:
[email protected]5990f9d2010-07-07 18:02:58532 gclient_utils.SyntaxErrorToError('.gclient', e)
[email protected]9a66ddf2010-06-16 16:54:16533 for s in config_dict.get('solutions', []):
[email protected]81843b82010-06-28 16:49:26534 try:
535 self.dependencies.append(Dependency(
536 self, s['name'], s['url'],
537 s.get('safesync_url', None),
538 s.get('custom_deps', {}),
539 s.get('custom_vars', {})))
540 except KeyError:
541 raise gclient_utils.Error('Invalid .gclient file. Solution is '
542 'incomplete: %s' % s)
[email protected]9a66ddf2010-06-16 16:54:16543 # .gclient can have hooks.
544 self.deps_hooks = config_dict.get('hooks', [])
545
546 def SaveConfig(self):
547 gclient_utils.FileWrite(os.path.join(self.root_dir(),
548 self._options.config_filename),
549 self.config_content)
550
551 @staticmethod
552 def LoadCurrentConfig(options):
553 """Searches for and loads a .gclient file relative to the current working
554 dir. Returns a GClient object."""
555 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
556 if not path:
557 return None
558 client = GClient(path, options)
559 client.SetConfig(gclient_utils.FileRead(
560 os.path.join(path, options.config_filename)))
561 return client
562
563 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
564 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
565 'solution_name': solution_name,
566 'solution_url': solution_url,
567 'safesync_url' : safesync_url,
568 })
569
[email protected]df2b3152010-07-21 17:35:24570 def _SaveEntries(self):
[email protected]9a66ddf2010-06-16 16:54:16571 """Creates a .gclient_entries file to record the list of unique checkouts.
572
573 The .gclient_entries file lives in the same directory as .gclient.
[email protected]9a66ddf2010-06-16 16:54:16574 """
575 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
576 # makes testing a bit too fun.
[email protected]df2b3152010-07-21 17:35:24577 result = 'entries = {\n'
578 for entry in self.tree(False):
579 # Skip over File() dependencies as we can't version them.
580 if not isinstance(entry.parsed_url, self.FileImpl):
581 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
582 pprint.pformat(entry.parsed_url))
583 result += '}\n'
[email protected]9a66ddf2010-06-16 16:54:16584 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
[email protected]df2b3152010-07-21 17:35:24585 logging.info(result)
586 gclient_utils.FileWrite(file_path, result)
[email protected]9a66ddf2010-06-16 16:54:16587
588 def _ReadEntries(self):
589 """Read the .gclient_entries file for the given client.
590
591 Returns:
592 A sequence of solution names, which will be empty if there is the
593 entries file hasn't been created yet.
594 """
595 scope = {}
596 filename = os.path.join(self.root_dir(), self._options.entries_filename)
597 if not os.path.exists(filename):
[email protected]73e21142010-07-05 13:32:01598 return {}
[email protected]5990f9d2010-07-07 18:02:58599 try:
600 exec(gclient_utils.FileRead(filename), scope)
601 except SyntaxError, e:
602 gclient_utils.SyntaxErrorToError(filename, e)
[email protected]9a66ddf2010-06-16 16:54:16603 return scope['entries']
604
[email protected]54a07a22010-06-14 19:07:39605 def _EnforceRevisions(self):
[email protected]918a9ae2010-05-28 15:50:30606 """Checks for revision overrides."""
607 revision_overrides = {}
[email protected]307d1792010-05-31 20:03:13608 if self._options.head:
609 return revision_overrides
[email protected]54a07a22010-06-14 19:07:39610 for s in self.dependencies:
611 if not s.safesync_url:
[email protected]307d1792010-05-31 20:03:13612 continue
[email protected]54a07a22010-06-14 19:07:39613 handle = urllib.urlopen(s.safesync_url)
[email protected]307d1792010-05-31 20:03:13614 rev = handle.read().strip()
615 handle.close()
616 if len(rev):
[email protected]54a07a22010-06-14 19:07:39617 self._options.revisions.append('%s@%s' % (s.name, rev))
[email protected]307d1792010-05-31 20:03:13618 if not self._options.revisions:
619 return revision_overrides
620 # --revision will take over safesync_url.
[email protected]54a07a22010-06-14 19:07:39621 solutions_names = [s.name for s in self.dependencies]
[email protected]307d1792010-05-31 20:03:13622 index = 0
623 for revision in self._options.revisions:
624 if not '@' in revision:
625 # Support for --revision 123
626 revision = '%s@%s' % (solutions_names[index], revision)
[email protected]54a07a22010-06-14 19:07:39627 sol, rev = revision.split('@', 1)
[email protected]307d1792010-05-31 20:03:13628 if not sol in solutions_names:
629 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
630 print >> sys.stderr, ('Please fix your script, having invalid '
631 '--revision flags will soon considered an error.')
632 else:
[email protected]918a9ae2010-05-28 15:50:30633 revision_overrides[sol] = rev
[email protected]307d1792010-05-31 20:03:13634 index += 1
[email protected]918a9ae2010-05-28 15:50:30635 return revision_overrides
636
[email protected]fb2b8eb2009-04-23 21:03:42637 def RunOnDeps(self, command, args):
638 """Runs a command on each dependency in a client and its dependencies.
639
[email protected]fb2b8eb2009-04-23 21:03:42640 Args:
641 command: The command to use (e.g., 'status' or 'diff')
642 args: list of str - extra arguments to add to the command line.
[email protected]fb2b8eb2009-04-23 21:03:42643 """
[email protected]54a07a22010-06-14 19:07:39644 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01645 raise gclient_utils.Error('No solution specified')
[email protected]54a07a22010-06-14 19:07:39646 revision_overrides = self._EnforceRevisions()
[email protected]df2b3152010-07-21 17:35:24647 pm = None
[email protected]d2e92562010-04-27 01:55:18648 if command == 'update' and not self._options.verbose:
[email protected]df2b3152010-07-21 17:35:24649 pm = Progress('Syncing projects', len(self.tree(False)) + 1)
650 self.RunCommandRecursively(self._options, revision_overrides,
651 command, args, pm)
652 if pm:
[email protected]d2e92562010-04-27 01:55:18653 pm.end()
[email protected]6f363722010-04-27 00:41:09654
[email protected]df2b3152010-07-21 17:35:24655 # Once all the dependencies have been processed, it's now safe to run the
656 # hooks.
657 if not self._options.nohooks:
658 self.RunHooksRecursively(self._options)
[email protected]fb2b8eb2009-04-23 21:03:42659
660 if command == 'update':
[email protected]cdcee802009-06-23 15:30:42661 # Notify the user if there is an orphaned entry in their working copy.
662 # Only delete the directory if there are no changes in it, and
663 # delete_unversioned_trees is set to true.
[email protected]df2b3152010-07-21 17:35:24664 entries = [i.name for i in self.tree(False)]
665 for entry, prev_url in self._ReadEntries().iteritems():
[email protected]c5e9aec2009-08-03 18:25:56666 # Fix path separator on Windows.
667 entry_fixed = entry.replace('/', os.path.sep)
[email protected]75a59272010-06-11 22:34:03668 e_dir = os.path.join(self.root_dir(), entry_fixed)
[email protected]c5e9aec2009-08-03 18:25:56669 # Use entry and not entry_fixed there.
[email protected]0329e672009-05-13 18:41:04670 if entry not in entries and os.path.exists(e_dir):
[email protected]df2b3152010-07-21 17:35:24671 file_list = []
672 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
673 scm.status(self._options, [], file_list)
674 modified_files = file_list != []
[email protected]83017012009-09-28 18:52:12675 if not self._options.delete_unversioned_trees or modified_files:
[email protected]c5e9aec2009-08-03 18:25:56676 # There are modified files in this entry. Keep warning until
677 # removed.
[email protected]d36fba82010-06-28 16:50:40678 print(('\nWARNING: \'%s\' is no longer part of this client. '
679 'It is recommended that you manually remove it.\n') %
[email protected]c5e9aec2009-08-03 18:25:56680 entry_fixed)
[email protected]fb2b8eb2009-04-23 21:03:42681 else:
682 # Delete the entry
[email protected]73e21142010-07-05 13:32:01683 print('\n________ deleting \'%s\' in \'%s\'' % (
684 entry_fixed, self.root_dir()))
[email protected]5f3eee32009-09-17 00:34:30685 gclient_utils.RemoveDirectory(e_dir)
[email protected]fb2b8eb2009-04-23 21:03:42686 # record the current list of entries for next time
[email protected]df2b3152010-07-21 17:35:24687 self._SaveEntries()
[email protected]17cdf762010-05-28 17:30:52688 return 0
[email protected]fb2b8eb2009-04-23 21:03:42689
690 def PrintRevInfo(self):
[email protected]5d63eb82010-03-24 23:22:09691 """Output revision info mapping for the client and its dependencies.
692
693 This allows the capture of an overall "revision" for the source tree that
[email protected]918a9ae2010-05-28 15:50:30694 can be used to reproduce the same tree in the future. It is only useful for
695 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
696 number or a git hash. A git branch name isn't "pinned" since the actual
697 commit can change.
[email protected]5d63eb82010-03-24 23:22:09698
699 The --snapshot option allows creating a .gclient file to reproduce the tree.
[email protected]fb2b8eb2009-04-23 21:03:42700 """
[email protected]54a07a22010-06-14 19:07:39701 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01702 raise gclient_utils.Error('No solution specified')
[email protected]df2b3152010-07-21 17:35:24703 # Load all the settings.
704 self.RunCommandRecursively(self._options, {}, None, [], None)
[email protected]fb2b8eb2009-04-23 21:03:42705
[email protected]fb2b8eb2009-04-23 21:03:42706 def GetURLAndRev(name, original_url):
[email protected]df2b3152010-07-21 17:35:24707 """Returns the revision-qualified SCM url."""
708 if original_url is None:
[email protected]baa578e2010-07-12 17:36:59709 return None
[email protected]df2b3152010-07-21 17:35:24710 if isinstance(original_url, self.FileImpl):
711 original_url = original_url.file_location
[email protected]5d63eb82010-03-24 23:22:09712 url, _ = gclient_utils.SplitUrlRevision(original_url)
[email protected]75a59272010-06-11 22:34:03713 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
[email protected]df2b3152010-07-21 17:35:24714 if not os.path.isdir(scm.checkout_path):
715 return None
[email protected]baa578e2010-07-12 17:36:59716 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
[email protected]fb2b8eb2009-04-23 21:03:42717
[email protected]baa578e2010-07-12 17:36:59718 if self._options.snapshot:
[email protected]df2b3152010-07-21 17:35:24719 new_gclient = ''
720 # First level at .gclient
721 for d in self.dependencies:
722 entries = {}
723 def GrabDeps(sol):
724 """Recursively grab dependencies."""
725 for i in sol.dependencies:
726 entries[i.name] = GetURLAndRev(i.name, i.parsed_url)
727 GrabDeps(i)
728 GrabDeps(d)
729 custom_deps = []
730 for k in sorted(entries.keys()):
731 if entries[k]:
732 # Quotes aren't escaped...
733 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
734 else:
735 custom_deps.append(' \"%s\": None,\n' % k)
736 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
737 'solution_name': d.name,
738 'solution_url': d.url,
739 'safesync_url' : d.safesync_url or '',
740 'solution_deps': ''.join(custom_deps),
741 }
742 # Print the snapshot configuration file
743 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
[email protected]de8f3522010-03-11 23:47:44744 else:
[email protected]df2b3152010-07-21 17:35:24745 entries = sorted(self.tree(False), key=lambda i: i.name)
746 for entry in entries:
747 entry_url = GetURLAndRev(entry.name, entry.parsed_url)
748 line = '%s: %s' % (entry.name, entry_url)
749 if not entry is entries[-1]:
750 line += ';'
751 print line
[email protected]fb2b8eb2009-04-23 21:03:42752
[email protected]d36fba82010-06-28 16:50:40753 def ParseDepsFile(self, direct_reference):
754 """No DEPS to parse for a .gclient file."""
[email protected]98d05fa2010-07-22 21:58:01755 self.direct_reference = True
[email protected]d36fba82010-06-28 16:50:40756 self.deps_parsed = True
757
[email protected]75a59272010-06-11 22:34:03758 def root_dir(self):
[email protected]d36fba82010-06-28 16:50:40759 """Root directory of gclient checkout."""
[email protected]75a59272010-06-11 22:34:03760 return self._root_dir
761
[email protected]271375b2010-06-23 19:17:38762 def enforced_os(self):
[email protected]d36fba82010-06-28 16:50:40763 """What deps_os entries that are to be parsed."""
[email protected]271375b2010-06-23 19:17:38764 return self._enforced_os
765
[email protected]d36fba82010-06-28 16:50:40766 def recursion_limit(self):
767 """How recursive can each dependencies in DEPS file can load DEPS file."""
768 return self._recursion_limit
769
770 def tree(self, force_all):
771 """Returns a flat list of all the dependencies."""
[email protected]c57e4f22010-07-22 21:37:46772 return self.subtree(force_all)
[email protected]d36fba82010-06-28 16:50:40773
[email protected]fb2b8eb2009-04-23 21:03:42774
[email protected]5ca27692010-05-26 19:32:41775#### gclient commands.
[email protected]fb2b8eb2009-04-23 21:03:42776
777
[email protected]5ca27692010-05-26 19:32:41778def CMDcleanup(parser, args):
779 """Cleans up all working copies.
[email protected]ddff62d2010-05-17 21:02:36780
[email protected]5ca27692010-05-26 19:32:41781Mostly svn-specific. Simply runs 'svn cleanup' for each module.
[email protected]79692d62010-05-14 18:57:13782"""
[email protected]0b6a0842010-06-15 14:34:19783 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
784 help='override deps for the specified (comma-separated) '
785 'platform(s); \'all\' will process all deps_os '
786 'references')
[email protected]5ca27692010-05-26 19:32:41787 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34788 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42789 if not client:
[email protected]0b6a0842010-06-15 14:34:19790 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42791 if options.verbose:
792 # Print out the .gclient file. This is longer than if we just printed the
793 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38794 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42795 return client.RunOnDeps('cleanup', args)
796
797
[email protected]4b90e3a2010-07-01 20:28:26798@attr('usage', '[command] [args ...]')
799def CMDrecurse(parser, args):
800 """Operates on all the entries.
801
802 Runs a shell command on all entries.
803 """
804 # Stop parsing at the first non-arg so that these go through to the command
805 parser.disable_interspersed_args()
806 parser.add_option('-s', '--scm', action='append', default=[],
807 help='choose scm types to operate upon')
808 options, args = parser.parse_args(args)
809 root, entries = gclient_utils.GetGClientRootAndEntries()
810 scm_set = set()
811 for scm in options.scm:
812 scm_set.update(scm.split(','))
813
814 # Pass in the SCM type as an env variable
815 env = os.environ.copy()
816
817 for path, url in entries.iteritems():
818 scm = gclient_scm.GetScmName(url)
819 if scm_set and scm not in scm_set:
820 continue
821 dir = os.path.normpath(os.path.join(root, path))
822 env['GCLIENT_SCM'] = scm
823 env['GCLIENT_URL'] = url
824 subprocess.Popen(args, cwd=dir, env=env).communicate()
825
826
[email protected]5ca27692010-05-26 19:32:41827@attr('usage', '[url] [safesync url]')
828def CMDconfig(parser, args):
[email protected]ddff62d2010-05-17 21:02:36829 """Create a .gclient file in the current directory.
830
[email protected]5ca27692010-05-26 19:32:41831This specifies the configuration for further commands. After update/sync,
[email protected]79692d62010-05-14 18:57:13832top-level DEPS files in each module are read to determine dependent
[email protected]5ca27692010-05-26 19:32:41833modules to operate on as well. If optional [url] parameter is
[email protected]79692d62010-05-14 18:57:13834provided, then configuration is read from a specified Subversion server
[email protected]5ca27692010-05-26 19:32:41835URL.
[email protected]79692d62010-05-14 18:57:13836"""
[email protected]0b6a0842010-06-15 14:34:19837 parser.add_option('--spec',
838 help='create a gclient file containing the provided '
839 'string. Due to Cygwin/Python brokenness, it '
840 'probably can\'t contain any newlines.')
841 parser.add_option('--name',
842 help='overrides the default name for the solution')
[email protected]5ca27692010-05-26 19:32:41843 (options, args) = parser.parse_args(args)
[email protected]5fc2a332010-05-26 19:37:15844 if ((options.spec and args) or len(args) > 2 or
845 (not options.spec and not args)):
846 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
847
[email protected]0329e672009-05-13 18:41:04848 if os.path.exists(options.config_filename):
[email protected]0b6a0842010-06-15 14:34:19849 raise gclient_utils.Error('%s file already exists in the current directory'
[email protected]e3608df2009-11-10 20:22:57850 % options.config_filename)
[email protected]2806acc2009-05-15 12:33:34851 client = GClient('.', options)
[email protected]fb2b8eb2009-04-23 21:03:42852 if options.spec:
853 client.SetConfig(options.spec)
854 else:
[email protected]1ab7ffc2009-06-03 17:21:37855 base_url = args[0].rstrip('/')
[email protected]8cf7a392010-04-07 17:20:26856 if not options.name:
[email protected]0b6a0842010-06-15 14:34:19857 name = base_url.split('/')[-1]
[email protected]8cf7a392010-04-07 17:20:26858 else:
859 # specify an alternate relpath for the given URL.
860 name = options.name
[email protected]0b6a0842010-06-15 14:34:19861 safesync_url = ''
[email protected]fb2b8eb2009-04-23 21:03:42862 if len(args) > 1:
863 safesync_url = args[1]
864 client.SetDefaultConfig(name, base_url, safesync_url)
865 client.SaveConfig()
[email protected]79692d62010-05-14 18:57:13866 return 0
[email protected]fb2b8eb2009-04-23 21:03:42867
868
[email protected]5ca27692010-05-26 19:32:41869def CMDexport(parser, args):
[email protected]ddff62d2010-05-17 21:02:36870 """Wrapper for svn export for all managed directories."""
[email protected]0b6a0842010-06-15 14:34:19871 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
872 help='override deps for the specified (comma-separated) '
873 'platform(s); \'all\' will process all deps_os '
874 'references')
[email protected]5ca27692010-05-26 19:32:41875 (options, args) = parser.parse_args(args)
[email protected]644aa0c2009-07-17 20:20:41876 if len(args) != 1:
[email protected]0b6a0842010-06-15 14:34:19877 raise gclient_utils.Error('Need directory name')
[email protected]644aa0c2009-07-17 20:20:41878 client = GClient.LoadCurrentConfig(options)
879
880 if not client:
[email protected]0b6a0842010-06-15 14:34:19881 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]644aa0c2009-07-17 20:20:41882
883 if options.verbose:
884 # Print out the .gclient file. This is longer than if we just printed the
885 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38886 print(client.config_content)
[email protected]644aa0c2009-07-17 20:20:41887 return client.RunOnDeps('export', args)
888
[email protected]fb2b8eb2009-04-23 21:03:42889
[email protected]5ca27692010-05-26 19:32:41890@attr('epilog', """Example:
891 gclient pack > patch.txt
892 generate simple patch for configured client and dependences
893""")
894def CMDpack(parser, args):
[email protected]79692d62010-05-14 18:57:13895 """Generate a patch which can be applied at the root of the tree.
[email protected]ddff62d2010-05-17 21:02:36896
[email protected]5ca27692010-05-26 19:32:41897Internally, runs 'svn diff'/'git diff' on each checked out module and
[email protected]79692d62010-05-14 18:57:13898dependencies, and performs minimal postprocessing of the output. The
899resulting patch is printed to stdout and can be applied to a freshly
[email protected]5ca27692010-05-26 19:32:41900checked out tree via 'patch -p0 < patchfile'.
[email protected]79692d62010-05-14 18:57:13901"""
[email protected]0b6a0842010-06-15 14:34:19902 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
903 help='override deps for the specified (comma-separated) '
904 'platform(s); \'all\' will process all deps_os '
905 'references')
[email protected]5ca27692010-05-26 19:32:41906 (options, args) = parser.parse_args(args)
[email protected]ab318592009-09-04 00:54:55907 client = GClient.LoadCurrentConfig(options)
908 if not client:
[email protected]0b6a0842010-06-15 14:34:19909 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]ab318592009-09-04 00:54:55910 if options.verbose:
911 # Print out the .gclient file. This is longer than if we just printed the
912 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38913 print(client.config_content)
[email protected]ab318592009-09-04 00:54:55914 return client.RunOnDeps('pack', args)
915
916
[email protected]5ca27692010-05-26 19:32:41917def CMDstatus(parser, args):
918 """Show modification status for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:19919 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
920 help='override deps for the specified (comma-separated) '
921 'platform(s); \'all\' will process all deps_os '
922 'references')
[email protected]5ca27692010-05-26 19:32:41923 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34924 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42925 if not client:
[email protected]0b6a0842010-06-15 14:34:19926 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42927 if options.verbose:
928 # Print out the .gclient file. This is longer than if we just printed the
929 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38930 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42931 return client.RunOnDeps('status', args)
932
933
[email protected]5ca27692010-05-26 19:32:41934@attr('epilog', """Examples:
[email protected]79692d62010-05-14 18:57:13935 gclient sync
936 update files from SCM according to current configuration,
937 *for modules which have changed since last update or sync*
938 gclient sync --force
939 update files from SCM according to current configuration, for
940 all modules (useful for recovering files deleted from local copy)
941 gclient sync --revision src@31000
942 update src directory to r31000
[email protected]5ca27692010-05-26 19:32:41943""")
944def CMDsync(parser, args):
945 """Checkout/update all modules."""
[email protected]0b6a0842010-06-15 14:34:19946 parser.add_option('-f', '--force', action='store_true',
947 help='force update even for unchanged modules')
948 parser.add_option('-n', '--nohooks', action='store_true',
949 help='don\'t run hooks after the update is complete')
950 parser.add_option('-r', '--revision', action='append',
951 dest='revisions', metavar='REV', default=[],
952 help='Enforces revision/hash for the solutions with the '
953 'format src@rev. The src@ part is optional and can be '
954 'skipped. -r can be used multiple times when .gclient '
955 'has multiple solutions configured and will work even '
956 'if the src@ part is skipped.')
957 parser.add_option('-H', '--head', action='store_true',
958 help='skips any safesync_urls specified in '
959 'configured solutions and sync to head instead')
960 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
961 help='delete any unexpected unversioned trees '
962 'that are in the checkout')
963 parser.add_option('-R', '--reset', action='store_true',
964 help='resets any local changes before updating (git only)')
965 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
966 help='override deps for the specified (comma-separated) '
967 'platform(s); \'all\' will process all deps_os '
968 'references')
969 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
970 help='Skip svn up whenever possible by requesting '
971 'actual HEAD revision from the repository')
[email protected]5ca27692010-05-26 19:32:41972 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34973 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42974
975 if not client:
[email protected]0b6a0842010-06-15 14:34:19976 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42977
[email protected]307d1792010-05-31 20:03:13978 if options.revisions and options.head:
979 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
[email protected]0b6a0842010-06-15 14:34:19980 print('Warning: you cannot use both --head and --revision')
[email protected]fb2b8eb2009-04-23 21:03:42981
982 if options.verbose:
983 # Print out the .gclient file. This is longer than if we just printed the
984 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38985 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42986 return client.RunOnDeps('update', args)
987
988
[email protected]5ca27692010-05-26 19:32:41989def CMDupdate(parser, args):
[email protected]ddff62d2010-05-17 21:02:36990 """Alias for the sync command. Deprecated."""
[email protected]5ca27692010-05-26 19:32:41991 return CMDsync(parser, args)
[email protected]fb2b8eb2009-04-23 21:03:42992
[email protected]5ca27692010-05-26 19:32:41993def CMDdiff(parser, args):
994 """Displays local diff for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:19995 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
996 help='override deps for the specified (comma-separated) '
997 'platform(s); \'all\' will process all deps_os '
998 'references')
[email protected]5ca27692010-05-26 19:32:41999 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341000 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421001 if not client:
[email protected]0b6a0842010-06-15 14:34:191002 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421003 if options.verbose:
1004 # Print out the .gclient file. This is longer than if we just printed the
1005 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381006 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421007 return client.RunOnDeps('diff', args)
1008
1009
[email protected]5ca27692010-05-26 19:32:411010def CMDrevert(parser, args):
1011 """Revert all modifications in every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191012 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1013 help='override deps for the specified (comma-separated) '
1014 'platform(s); \'all\' will process all deps_os '
1015 'references')
1016 parser.add_option('-n', '--nohooks', action='store_true',
1017 help='don\'t run hooks after the revert is complete')
[email protected]5ca27692010-05-26 19:32:411018 (options, args) = parser.parse_args(args)
1019 # --force is implied.
1020 options.force = True
[email protected]2806acc2009-05-15 12:33:341021 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421022 if not client:
[email protected]0b6a0842010-06-15 14:34:191023 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421024 return client.RunOnDeps('revert', args)
1025
1026
[email protected]5ca27692010-05-26 19:32:411027def CMDrunhooks(parser, args):
1028 """Runs hooks for files that have been modified in the local working copy."""
[email protected]0b6a0842010-06-15 14:34:191029 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1030 help='override deps for the specified (comma-separated) '
1031 'platform(s); \'all\' will process all deps_os '
1032 'references')
1033 parser.add_option('-f', '--force', action='store_true', default=True,
1034 help='Deprecated. No effect.')
[email protected]5ca27692010-05-26 19:32:411035 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341036 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421037 if not client:
[email protected]0b6a0842010-06-15 14:34:191038 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421039 if options.verbose:
1040 # Print out the .gclient file. This is longer than if we just printed the
1041 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381042 print(client.config_content)
[email protected]5df6a462009-08-28 18:52:261043 options.force = True
[email protected]5ca27692010-05-26 19:32:411044 options.nohooks = False
[email protected]fb2b8eb2009-04-23 21:03:421045 return client.RunOnDeps('runhooks', args)
1046
1047
[email protected]5ca27692010-05-26 19:32:411048def CMDrevinfo(parser, args):
[email protected]9eda4112010-06-11 18:56:101049 """Output revision info mapping for the client and its dependencies.
1050
[email protected]0b6a0842010-06-15 14:34:191051 This allows the capture of an overall 'revision' for the source tree that
[email protected]9eda4112010-06-11 18:56:101052 can be used to reproduce the same tree in the future. It is only useful for
[email protected]0b6a0842010-06-15 14:34:191053 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1054 number or a git hash. A git branch name isn't 'pinned' since the actual
[email protected]9eda4112010-06-11 18:56:101055 commit can change.
1056 """
1057 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1058 help='override deps for the specified (comma-separated) '
1059 'platform(s); \'all\' will process all deps_os '
1060 'references')
1061 parser.add_option('-s', '--snapshot', action='store_true',
1062 help='creates a snapshot .gclient file of the current '
[email protected]df2b3152010-07-21 17:35:241063 'version of all repositories to reproduce the tree')
[email protected]5ca27692010-05-26 19:32:411064 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341065 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421066 if not client:
[email protected]0b6a0842010-06-15 14:34:191067 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421068 client.PrintRevInfo()
[email protected]79692d62010-05-14 18:57:131069 return 0
[email protected]fb2b8eb2009-04-23 21:03:421070
1071
[email protected]5ca27692010-05-26 19:32:411072def Command(name):
1073 return getattr(sys.modules[__name__], 'CMD' + name, None)
1074
1075
1076def CMDhelp(parser, args):
1077 """Prints list of commands or help for a specific command."""
[email protected]6e29d572010-06-04 17:32:201078 (_, args) = parser.parse_args(args)
[email protected]ddff62d2010-05-17 21:02:361079 if len(args) == 1:
[email protected]5ca27692010-05-26 19:32:411080 return Main(args + ['--help'])
[email protected]ddff62d2010-05-17 21:02:361081 parser.print_help()
1082 return 0
1083
1084
[email protected]5ca27692010-05-26 19:32:411085def GenUsage(parser, command):
1086 """Modify an OptParse object with the function's documentation."""
1087 obj = Command(command)
1088 if command == 'help':
1089 command = '<command>'
1090 # OptParser.description prefer nicely non-formatted strings.
1091 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1092 usage = getattr(obj, 'usage', '')
1093 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1094 parser.epilog = getattr(obj, 'epilog', None)
[email protected]fb2b8eb2009-04-23 21:03:421095
1096
1097def Main(argv):
[email protected]5ca27692010-05-26 19:32:411098 """Doesn't parse the arguments here, just find the right subcommand to
1099 execute."""
[email protected]6e29d572010-06-04 17:32:201100 try:
1101 # Do it late so all commands are listed.
1102 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1103 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1104 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1105 parser = optparse.OptionParser(version='%prog ' + __version__)
[email protected]f0fc9912010-06-11 17:57:331106 parser.add_option('-v', '--verbose', action='count', default=0,
1107 help='Produces additional output for diagnostics. Can be '
1108 'used up to three times for more logging info.')
1109 parser.add_option('--gclientfile', dest='config_filename',
1110 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1111 help='Specify an alternate %default file')
[email protected]6e29d572010-06-04 17:32:201112 # Integrate standard options processing.
1113 old_parser = parser.parse_args
1114 def Parse(args):
1115 (options, args) = old_parser(args)
[email protected]f0fc9912010-06-11 17:57:331116 level = None
[email protected]6e29d572010-06-04 17:32:201117 if options.verbose == 2:
[email protected]f0fc9912010-06-11 17:57:331118 level = logging.INFO
[email protected]6e29d572010-06-04 17:32:201119 elif options.verbose > 2:
[email protected]f0fc9912010-06-11 17:57:331120 level = logging.DEBUG
1121 logging.basicConfig(level=level,
1122 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1123 options.entries_filename = options.config_filename + '_entries'
[email protected]e3216c62010-07-08 03:31:431124
1125 # These hacks need to die.
[email protected]6e29d572010-06-04 17:32:201126 if not hasattr(options, 'revisions'):
1127 # GClient.RunOnDeps expects it even if not applicable.
1128 options.revisions = []
1129 if not hasattr(options, 'head'):
1130 options.head = None
[email protected]f0fc9912010-06-11 17:57:331131 if not hasattr(options, 'nohooks'):
1132 options.nohooks = True
1133 if not hasattr(options, 'deps_os'):
1134 options.deps_os = None
[email protected]e3216c62010-07-08 03:31:431135 if not hasattr(options, 'manually_grab_svn_rev'):
1136 options.manually_grab_svn_rev = None
1137 if not hasattr(options, 'force'):
1138 options.force = None
[email protected]6e29d572010-06-04 17:32:201139 return (options, args)
1140 parser.parse_args = Parse
1141 # We don't want wordwrapping in epilog (usually examples)
1142 parser.format_epilog = lambda _: parser.epilog or ''
1143 if argv:
1144 command = Command(argv[0])
1145 if command:
[email protected]f0fc9912010-06-11 17:57:331146 # 'fix' the usage and the description now that we know the subcommand.
[email protected]6e29d572010-06-04 17:32:201147 GenUsage(parser, argv[0])
1148 return command(parser, argv[1:])
1149 # Not a known command. Default to help.
1150 GenUsage(parser, 'help')
1151 return CMDhelp(parser, argv)
1152 except gclient_utils.Error, e:
[email protected]f0fc9912010-06-11 17:57:331153 print >> sys.stderr, 'Error: %s' % str(e)
[email protected]6e29d572010-06-04 17:32:201154 return 1
[email protected]fb2b8eb2009-04-23 21:03:421155
1156
[email protected]f0fc9912010-06-11 17:57:331157if '__main__' == __name__:
[email protected]6e29d572010-06-04 17:32:201158 sys.exit(Main(sys.argv[1:]))
[email protected]fb2b8eb2009-04-23 21:03:421159
1160# vim: ts=2:sw=2:tw=80:et: