blob: 80c1217b1bdfbca16a8c79efaaa1bc9883d38145 [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
maruel@chromium.orgba551772010-02-03 18:21:42 +00002# 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.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
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
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --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
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 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.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000052__version__ = "0.4.1"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
54import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000055import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056import optparse
57import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000058import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000059import re
piman@chromium.org4b90e3a2010-07-01 20:28:26 +000060import subprocess
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urllib
64
maruel@chromium.orgada4c652009-12-03 15:32:01 +000065import breakpad
66
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000067import gclient_scm
68import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000069from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000072def attr(attr, data):
73 """Sets an attribute on a function."""
74 def hook(fn):
75 setattr(fn, attr, data)
76 return fn
77 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000078
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080## GClient implementation.
81
82
maruel@chromium.org116704f2010-06-11 17:34:38 +000083class GClientKeywords(object):
84 class FromImpl(object):
85 """Used to implement the From() syntax."""
86
87 def __init__(self, module_name, sub_target_name=None):
88 """module_name is the dep module we want to include from. It can also be
89 the name of a subdirectory to include from.
90
91 sub_target_name is an optional parameter if the module name in the other
92 DEPS file is different. E.g., you might want to map src/net to net."""
93 self.module_name = module_name
94 self.sub_target_name = sub_target_name
95
96 def __str__(self):
97 return 'From(%s, %s)' % (repr(self.module_name),
98 repr(self.sub_target_name))
99
100 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
101 """Resolve the URL for this From entry."""
102 sub_deps_target_name = target_name
103 if self.sub_target_name:
104 sub_deps_target_name = self.sub_target_name
105 url = sub_deps[sub_deps_target_name]
106 if url.startswith('/'):
107 # If it's a relative URL, we need to resolve the URL relative to the
108 # sub deps base URL.
109 if not isinstance(sub_deps_base_url, basestring):
110 sub_deps_base_url = sub_deps_base_url.GetPath()
111 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
112 None)
113 url = scm.FullUrlForRelativeUrl(url)
114 return url
115
116 class FileImpl(object):
117 """Used to implement the File('') syntax which lets you sync a single file
118 from an SVN repo."""
119
120 def __init__(self, file_location):
121 self.file_location = file_location
122
123 def __str__(self):
124 return 'File("%s")' % self.file_location
125
126 def GetPath(self):
127 return os.path.split(self.file_location)[0]
128
129 def GetFilename(self):
130 rev_tokens = self.file_location.split('@')
131 return os.path.split(rev_tokens[0])[1]
132
133 def GetRevision(self):
134 rev_tokens = self.file_location.split('@')
135 if len(rev_tokens) > 1:
136 return rev_tokens[1]
137 return None
138
139 class VarImpl(object):
140 def __init__(self, custom_vars, local_scope):
141 self._custom_vars = custom_vars
142 self._local_scope = local_scope
143
144 def Lookup(self, var_name):
145 """Implements the Var syntax."""
146 if var_name in self._custom_vars:
147 return self._custom_vars[var_name]
148 elif var_name in self._local_scope.get("vars", {}):
149 return self._local_scope["vars"][var_name]
150 raise gclient_utils.Error("Var is not defined: %s" % var_name)
151
152
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000153class Dependency(GClientKeywords):
154 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000155 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000156
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000157 def __init__(self, parent, name, url, safesync_url=None, custom_deps=None,
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000158 custom_vars=None, deps_file=None):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000159 GClientKeywords.__init__(self)
160 self.parent = parent
161 self.name = name
162 self.url = url
163 # These 2 are only set in .gclient and not in DEPS files.
164 self.safesync_url = safesync_url
165 self.custom_vars = custom_vars or {}
166 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000167 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000168 self.dependencies = []
169 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.org271375b2010-06-23 19:17:38 +0000170 self.deps_parsed = False
171 self.direct_reference = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000172
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000173 # Sanity checks
174 if not self.name and self.parent:
175 raise gclient_utils.Error('Dependency without name')
176 if not isinstance(self.url,
177 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
178 raise gclient_utils.Error('dependency url must be either a string, None, '
179 'File() or From() instead of %s' %
180 self.url.__class__.__name__)
181 if '/' in self.deps_file or '\\' in self.deps_file:
182 raise gclient_utils.Error('deps_file name must not be a path, just a '
183 'filename. %s' % self.deps_file)
184
maruel@chromium.org271375b2010-06-23 19:17:38 +0000185 def ParseDepsFile(self, direct_reference):
186 """Parses the DEPS file for this dependency."""
187 if direct_reference:
188 # Maybe it was referenced earlier by a From() keyword but it's now
189 # directly referenced.
190 self.direct_reference = direct_reference
191 self.deps_parsed = True
192 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
193 if not os.path.isfile(filepath):
maruel@chromium.org0d425922010-06-21 19:22:24 +0000194 return {}
maruel@chromium.org271375b2010-06-23 19:17:38 +0000195 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000196
maruel@chromium.org271375b2010-06-23 19:17:38 +0000197 # Eval the content.
198 # One thing is unintuitive, vars= {} must happen before Var() use.
199 local_scope = {}
200 var = self.VarImpl(self.custom_vars, local_scope)
201 global_scope = {
202 'File': self.FileImpl,
203 'From': self.FromImpl,
204 'Var': var.Lookup,
205 'deps_os': {},
206 }
207 exec(deps_content, global_scope, local_scope)
208 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000209 # load os specific dependencies if defined. these dependencies may
210 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000211 if 'deps_os' in local_scope:
212 for deps_os_key in self.enforced_os():
213 os_deps = local_scope['deps_os'].get(deps_os_key, {})
214 if len(self.enforced_os()) > 1:
215 # Ignore any conflict when including deps for more than one
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000216 # platform, so we collect the broadest set of dependencies available.
217 # We may end up with the wrong revision of something for our
218 # platform, but this is the best we can do.
219 deps.update([x for x in os_deps.items() if not x[0] in deps])
220 else:
221 deps.update(os_deps)
222
maruel@chromium.org271375b2010-06-23 19:17:38 +0000223 self.deps_hooks.extend(local_scope.get('hooks', []))
224
225 # If a line is in custom_deps, but not in the solution, we want to append
226 # this line to the solution.
227 for d in self.custom_deps:
228 if d not in deps:
229 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000230
231 # If use_relative_paths is set in the DEPS file, regenerate
232 # the dictionary using paths relative to the directory containing
233 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000234 use_relative_paths = local_scope.get('use_relative_paths', False)
235 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000236 rel_deps = {}
237 for d, url in deps.items():
238 # normpath is required to allow DEPS to use .. in their
239 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000240 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
241 deps = rel_deps
242 # TODO(maruel): Add these dependencies into self.dependencies.
243 return deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000244
maruel@chromium.org271375b2010-06-23 19:17:38 +0000245 def _ParseAllDeps(self, solution_urls):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000246 """Parse the complete list of dependencies for the client.
247
248 Args:
249 solution_urls: A dict mapping module names (as relative paths) to URLs
250 corresponding to the solutions specified by the client. This parameter
251 is passed as an optimization.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000252
253 Returns:
254 A dict mapping module names (as relative paths) to URLs corresponding
255 to the entire set of dependencies to checkout for the given client.
256
257 Raises:
258 Error: If a dependency conflicts with another dependency or of a solution.
259 """
260 deps = {}
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000261 for solution in self.dependencies:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000262 solution_deps = solution.ParseDepsFile(True)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000263
264 # If a line is in custom_deps, but not in the solution, we want to append
265 # this line to the solution.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000266 for d in solution.custom_deps:
267 if d not in solution_deps:
268 solution_deps[d] = solution.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000269
270 for d in solution_deps:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000271 if d in solution.custom_deps:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000272 # Dependency is overriden.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000273 url = solution.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000274 if url is None:
275 continue
276 else:
277 url = solution_deps[d]
278 # if we have a From reference dependent on another solution, then
279 # just skip the From reference. When we pull deps for the solution,
280 # we will take care of this dependency.
281 #
282 # If multiple solutions all have the same From reference, then we
283 # should only add one to our list of dependencies.
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000284 if isinstance(url, self.FromImpl):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000285 if url.module_name in solution_urls:
286 # Already parsed.
287 continue
288 if d in deps and type(deps[d]) != str:
289 if url.module_name == deps[d].module_name:
290 continue
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000291 elif isinstance(url, str):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000292 parsed_url = urlparse.urlparse(url)
293 scheme = parsed_url[0]
294 if not scheme:
295 # A relative url. Fetch the real base.
296 path = parsed_url[2]
297 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000298 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000299 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000300 # Create a scm just to query the full url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000301 scm = gclient_scm.CreateSCM(solution.url, self.root_dir(),
302 None)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000303 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000304 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000305 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000306 "Solutions have conflicting versions of dependency \"%s\"" % d)
307 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000308 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000309 "Dependency \"%s\" conflicts with specified solution" % d)
310 # Grab the dependency.
311 deps[d] = url
312 return deps
313
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000314 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000315 """Runs the action from a single hook."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000316 logging.info(hook_dict)
317 logging.info(matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000318 command = hook_dict['action'][:]
319 if command[0] == 'python':
320 # If the hook specified "python" as the first item, the action is a
321 # Python script. Run it by starting a new copy of the same
322 # interpreter.
323 command[0] = sys.executable
324
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000325 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000326 splice_index = command.index('$matching_files')
327 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000328
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000329 # Use a discrete exit status code of 2 to indicate that a hook action
330 # failed. Users of this script may wish to treat hook action failures
331 # differently from VC failures.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000332 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000333
334 def _RunHooks(self, command, file_list, is_using_git):
335 """Evaluates all hooks, running actions as needed.
336 """
337 # Hooks only run for these command types.
338 if not command in ('update', 'revert', 'runhooks'):
339 return
340
evan@chromium.org67820ef2009-07-27 17:23:00 +0000341 # Hooks only run when --nohooks is not specified
342 if self._options.nohooks:
343 return
344
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000345 # Get any hooks from the .gclient file.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000346 hooks = self.deps_hooks[:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000347 # Add any hooks found in DEPS files.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000348 for d in self.dependencies:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000349 hooks.extend(d.deps_hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000350
351 # If "--force" was specified, run all hooks regardless of what files have
352 # changed. If the user is using git, then we don't know what files have
353 # changed so we always run all hooks.
354 if self._options.force or is_using_git:
355 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000356 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000357 return
358
359 # Run hooks on the basis of whether the files from the gclient operation
360 # match each hook's pattern.
361 for hook_dict in hooks:
362 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000363 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000364 if matching_file_list:
365 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000366
maruel@chromium.org271375b2010-06-23 19:17:38 +0000367 def root_dir(self):
368 return self.parent.root_dir()
369
370 def enforced_os(self):
371 return self.parent.enforced_os()
372
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000373 def recursion_limit(self):
374 return self.parent.recursion_limit() - 1
375
376 def tree(self, force_all):
377 return self.parent.tree(force_all)
378
379 def get_custom_deps(self, name, url):
380 """Returns a custom deps if applicable."""
381 if self.parent:
382 url = self.parent.get_custom_deps(name, url)
383 # None is a valid return value to disable a dependency.
384 return self.custom_deps.get(name, url)
385
386 def __str__(self):
387 out = []
388 for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars',
389 'deps_hooks'):
390 # 'deps_file'
391 if self.__dict__[i]:
392 out.append('%s: %s' % (i, self.__dict__[i]))
393
394 for d in self.dependencies:
395 out.extend([' ' + x for x in str(d).splitlines()])
396 out.append('')
397 return '\n'.join(out)
398
399 def __repr__(self):
400 return '%s: %s' % (self.name, self.url)
401
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000402
403class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000404 """Object that represent a gclient checkout. A tree of Dependency(), one per
405 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000406 SUPPORTED_COMMANDS = [
407 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
408 'runhooks'
409 ]
410
411 DEPS_OS_CHOICES = {
412 "win32": "win",
413 "win": "win",
414 "cygwin": "win",
415 "darwin": "mac",
416 "mac": "mac",
417 "unix": "unix",
418 "linux": "unix",
419 "linux2": "unix",
420 }
421
422 DEFAULT_CLIENT_FILE_TEXT = ("""\
423solutions = [
424 { "name" : "%(solution_name)s",
425 "url" : "%(solution_url)s",
426 "custom_deps" : {
427 },
428 "safesync_url": "%(safesync_url)s"
429 },
430]
431""")
432
433 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
434 { "name" : "%(solution_name)s",
435 "url" : "%(solution_url)s",
436 "custom_deps" : {
437 %(solution_deps)s,
438 },
439 "safesync_url": "%(safesync_url)s"
440 },
441""")
442
443 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
444# Snapshot generated with gclient revinfo --snapshot
445solutions = [
446%(solution_list)s
447]
448""")
449
450 def __init__(self, root_dir, options):
451 Dependency.__init__(self, None, None, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000452 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000453 if options.deps_os:
454 enforced_os = options.deps_os.split(',')
455 else:
456 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
457 if 'all' in enforced_os:
458 enforced_os = self.DEPS_OS_CHOICES.itervalues()
459 self._enforced_os = list(set(enforced_os))
460 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000461 self.config_content = None
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000462 # Do not change previous behavior. Only solution level and immediate DEPS
463 # are processed.
464 self._recursion_limit = 2
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000465
466 def SetConfig(self, content):
467 assert self.dependencies == []
468 config_dict = {}
469 self.config_content = content
470 try:
471 exec(content, config_dict)
472 except SyntaxError, e:
473 try:
474 # Try to construct a human readable error message
475 error_message = [
476 'There is a syntax error in your configuration file.',
477 'Line #%s, character %s:' % (e.lineno, e.offset),
478 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
479 except:
480 # Something went wrong, re-raise the original exception
481 raise e
482 else:
483 # Raise a new exception with the human readable message:
484 raise gclient_utils.Error('\n'.join(error_message))
485 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000486 try:
487 self.dependencies.append(Dependency(
488 self, s['name'], s['url'],
489 s.get('safesync_url', None),
490 s.get('custom_deps', {}),
491 s.get('custom_vars', {})))
492 except KeyError:
493 raise gclient_utils.Error('Invalid .gclient file. Solution is '
494 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000495 # .gclient can have hooks.
496 self.deps_hooks = config_dict.get('hooks', [])
497
498 def SaveConfig(self):
499 gclient_utils.FileWrite(os.path.join(self.root_dir(),
500 self._options.config_filename),
501 self.config_content)
502
503 @staticmethod
504 def LoadCurrentConfig(options):
505 """Searches for and loads a .gclient file relative to the current working
506 dir. Returns a GClient object."""
507 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
508 if not path:
509 return None
510 client = GClient(path, options)
511 client.SetConfig(gclient_utils.FileRead(
512 os.path.join(path, options.config_filename)))
513 return client
514
515 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
516 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
517 'solution_name': solution_name,
518 'solution_url': solution_url,
519 'safesync_url' : safesync_url,
520 })
521
522 def _SaveEntries(self, entries):
523 """Creates a .gclient_entries file to record the list of unique checkouts.
524
525 The .gclient_entries file lives in the same directory as .gclient.
526
527 Args:
528 entries: A sequence of solution names.
529 """
530 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
531 # makes testing a bit too fun.
532 result = pprint.pformat(entries, 2)
533 if result.startswith('{\''):
534 result = '{ \'' + result[2:]
535 text = "entries = \\\n" + result + '\n'
536 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
537 gclient_utils.FileWrite(file_path, text)
538
539 def _ReadEntries(self):
540 """Read the .gclient_entries file for the given client.
541
542 Returns:
543 A sequence of solution names, which will be empty if there is the
544 entries file hasn't been created yet.
545 """
546 scope = {}
547 filename = os.path.join(self.root_dir(), self._options.entries_filename)
548 if not os.path.exists(filename):
549 return []
550 exec(gclient_utils.FileRead(filename), scope)
551 return scope['entries']
552
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000553 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000554 """Checks for revision overrides."""
555 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000556 if self._options.head:
557 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000558 for s in self.dependencies:
559 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000560 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000561 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000562 rev = handle.read().strip()
563 handle.close()
564 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000565 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000566 if not self._options.revisions:
567 return revision_overrides
568 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000569 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000570 index = 0
571 for revision in self._options.revisions:
572 if not '@' in revision:
573 # Support for --revision 123
574 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000575 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000576 if not sol in solutions_names:
577 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
578 print >> sys.stderr, ('Please fix your script, having invalid '
579 '--revision flags will soon considered an error.')
580 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000581 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000582 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000583 return revision_overrides
584
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000585 def RunOnDeps(self, command, args):
586 """Runs a command on each dependency in a client and its dependencies.
587
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000588 Args:
589 command: The command to use (e.g., 'status' or 'diff')
590 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000591 """
maruel@chromium.org116704f2010-06-11 17:34:38 +0000592 if not command in self.SUPPORTED_COMMANDS:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000593 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000594
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000595 if not self.dependencies:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000596 raise gclient_utils.Error("No solution specified")
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000597 revision_overrides = self._EnforceRevisions()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000598
599 # When running runhooks --force, there's no need to consult the SCM.
600 # All known hooks are expected to run unconditionally regardless of working
601 # copy state, so skip the SCM status check.
602 run_scm = not (command == 'runhooks' and self._options.force)
603
604 entries = {}
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000605 file_list = []
606 # Run on the base solutions first.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000607 for solution in self.dependencies:
608 name = solution.name
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000609 if name in entries:
610 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000611 url = solution.url
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000612 entries[name] = url
613 if run_scm and url:
614 self._options.revision = revision_overrides.get(name)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000615 scm = gclient_scm.CreateSCM(url, self.root_dir(), name)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000616 scm.RunCommand(command, self._options, args, file_list)
617 file_list = [os.path.join(name, f.strip()) for f in file_list]
618 self._options.revision = None
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000619
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000620 # Process the dependencies next (sort alphanumerically to ensure that
621 # containing directories get populated first and for readability)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000622 deps = self._ParseAllDeps(entries)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000623 deps_to_process = deps.keys()
624 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000625
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000626 # First pass for direct dependencies.
627 if command == 'update' and not self._options.verbose:
628 pm = Progress('Syncing projects', len(deps_to_process))
629 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000630 if command == 'update' and not self._options.verbose:
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000631 pm.update()
632 if type(deps[d]) == str:
633 url = deps[d]
634 entries[d] = url
635 if run_scm:
636 self._options.revision = revision_overrides.get(d)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000637 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000638 scm.RunCommand(command, self._options, args, file_list)
639 self._options.revision = None
640 elif isinstance(deps[d], self.FileImpl):
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000641 file_dep = deps[d]
642 self._options.revision = file_dep.GetRevision()
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000643 if run_scm:
maruel@chromium.org75a59272010-06-11 22:34:03 +0000644 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000645 scm.RunCommand("updatesingle", self._options,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000646 args + [file_dep.GetFilename()], file_list)
maruel@chromium.org79692d62010-05-14 18:57:13 +0000647
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000648 if command == 'update' and not self._options.verbose:
649 pm.end()
piman@chromium.org6f363722010-04-27 00:41:09 +0000650
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000651 # Second pass for inherited deps (via the From keyword)
652 for d in deps_to_process:
653 if isinstance(deps[d], self.FromImpl):
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000654 # Getting the URL from the sub_deps file can involve having to resolve
655 # a File() or having to resolve a relative URL. To resolve relative
656 # URLs, we need to pass in the orignal sub deps URL.
657 sub_deps_base_url = deps[deps[d].module_name]
maruel@chromium.org271375b2010-06-23 19:17:38 +0000658 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
659 ).ParseDepsFile(False)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000660 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000661 entries[d] = url
662 if run_scm:
663 self._options.revision = revision_overrides.get(d)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000664 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000665 scm.RunCommand(command, self._options, args, file_list)
666 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000667
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000668 # Convert all absolute paths to relative.
669 for i in range(len(file_list)):
670 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
671 # It depends on the command being executed (like runhooks vs sync).
672 if not os.path.isabs(file_list[i]):
673 continue
674
maruel@chromium.org75a59272010-06-11 22:34:03 +0000675 prefix = os.path.commonprefix([self.root_dir().lower(),
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000676 file_list[i].lower()])
677 file_list[i] = file_list[i][len(prefix):]
678
679 # Strip any leading path separators.
680 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
681 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000682
maruel@chromium.org75a59272010-06-11 22:34:03 +0000683 is_using_git = gclient_utils.IsUsingGit(self.root_dir(), entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000684 self._RunHooks(command, file_list, is_using_git)
685
686 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000687 # Notify the user if there is an orphaned entry in their working copy.
688 # Only delete the directory if there are no changes in it, and
689 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000690 prev_entries = self._ReadEntries()
691 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000692 # Fix path separator on Windows.
693 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000694 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000695 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000696 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000697 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000698 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000699 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000700 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000701 else:
702 file_list = []
maruel@chromium.org75a59272010-06-11 22:34:03 +0000703 scm = gclient_scm.CreateSCM(prev_entries[entry], self.root_dir(),
msb@chromium.org83017012009-09-28 18:52:12 +0000704 entry_fixed)
705 scm.status(self._options, [], file_list)
706 modified_files = file_list != []
707 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000708 # There are modified files in this entry. Keep warning until
709 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000710 print(('\nWARNING: \'%s\' is no longer part of this client. '
711 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000712 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000713 else:
714 # Delete the entry
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000715 print('\n________ deleting \'%s\' ' +
716 'in \'%s\'') % (entry_fixed, self.root_dir())
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000717 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000718 # record the current list of entries for next time
719 self._SaveEntries(entries)
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000720 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000721
722 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000723 """Output revision info mapping for the client and its dependencies.
724
725 This allows the capture of an overall "revision" for the source tree that
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000726 can be used to reproduce the same tree in the future. It is only useful for
727 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
728 number or a git hash. A git branch name isn't "pinned" since the actual
729 commit can change.
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000730
731 The --snapshot option allows creating a .gclient file to reproduce the tree.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000732 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000733 if not self.dependencies:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000734 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000735
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000736 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000737 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000738 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000739 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000740 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000741
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000742 # text of the snapshot gclient file
743 new_gclient = ""
744 # Dictionary of { path : SCM url } to ensure no duplicate solutions
745 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000746 entries = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000747 # Run on the base solutions first.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000748 for solution in self.dependencies:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000749 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000750 name = solution.name
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000751 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000752 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000753 (url, rev) = GetURLAndRev(name, solution.url)
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000754 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000755 solution_names[name] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000756
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000757 # Process the dependencies next (sort alphanumerically to ensure that
758 # containing directories get populated first and for readability)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000759 deps = self._ParseAllDeps(entries)
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000760 deps_to_process = deps.keys()
761 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000762
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000763 # First pass for direct dependencies.
764 for d in deps_to_process:
765 if type(deps[d]) == str:
766 (url, rev) = GetURLAndRev(d, deps[d])
767 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000768
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000769 # Second pass for inherited deps (via the From keyword)
770 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000771 if isinstance(deps[d], self.FromImpl):
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000772 deps_parent_url = entries[deps[d].module_name]
773 if deps_parent_url.find("@") < 0:
774 raise gclient_utils.Error("From %s missing revisioned url" %
775 deps[d].module_name)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000776 sub_deps_base_url = deps[deps[d].module_name]
777 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
778 ).ParseDepsFile(False)
779 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
780 (url, rev) = GetURLAndRev(d, url)
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000781 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000782
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000783 # Build the snapshot configuration string
784 if self._options.snapshot:
785 url = entries.pop(name)
786 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
787 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000788
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000789 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000790 'solution_name': name,
791 'solution_url': url,
792 'safesync_url' : "",
793 'solution_deps': custom_deps,
794 }
795 else:
796 print(";\n".join(["%s: %s" % (x, entries[x])
797 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000798
799 # Print the snapshot configuration file
800 if self._options.snapshot:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000801 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
maruel@chromium.org75a59272010-06-11 22:34:03 +0000802 snapclient = GClient(self.root_dir(), self._options)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000803 snapclient.SetConfig(config)
maruel@chromium.org116704f2010-06-11 17:34:38 +0000804 print(snapclient.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000805
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000806 def ParseDepsFile(self, direct_reference):
807 """No DEPS to parse for a .gclient file."""
808 self.direct_reference = direct_reference
809 self.deps_parsed = True
810
maruel@chromium.org75a59272010-06-11 22:34:03 +0000811 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000812 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000813 return self._root_dir
814
maruel@chromium.org271375b2010-06-23 19:17:38 +0000815 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000816 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000817 return self._enforced_os
818
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000819 def recursion_limit(self):
820 """How recursive can each dependencies in DEPS file can load DEPS file."""
821 return self._recursion_limit
822
823 def tree(self, force_all):
824 """Returns a flat list of all the dependencies."""
825 def subtree(dep):
826 if not force_all and not dep.direct_reference:
827 # Was loaded from a From() keyword in a DEPS file, don't load all its
828 # dependencies.
829 return []
830 result = dep.dependencies[:]
831 for d in dep.dependencies:
832 result.extend(subtree(d))
833 return result
834 return subtree(self)
835
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000836
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000837#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000838
839
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000840def CMDcleanup(parser, args):
841 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000842
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000843Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000844"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000845 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
846 help='override deps for the specified (comma-separated) '
847 'platform(s); \'all\' will process all deps_os '
848 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000849 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000850 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000851 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000852 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000853 if options.verbose:
854 # Print out the .gclient file. This is longer than if we just printed the
855 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000856 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000857 return client.RunOnDeps('cleanup', args)
858
859
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000860@attr('usage', '[command] [args ...]')
861def CMDrecurse(parser, args):
862 """Operates on all the entries.
863
864 Runs a shell command on all entries.
865 """
866 # Stop parsing at the first non-arg so that these go through to the command
867 parser.disable_interspersed_args()
868 parser.add_option('-s', '--scm', action='append', default=[],
869 help='choose scm types to operate upon')
870 options, args = parser.parse_args(args)
871 root, entries = gclient_utils.GetGClientRootAndEntries()
872 scm_set = set()
873 for scm in options.scm:
874 scm_set.update(scm.split(','))
875
876 # Pass in the SCM type as an env variable
877 env = os.environ.copy()
878
879 for path, url in entries.iteritems():
880 scm = gclient_scm.GetScmName(url)
881 if scm_set and scm not in scm_set:
882 continue
883 dir = os.path.normpath(os.path.join(root, path))
884 env['GCLIENT_SCM'] = scm
885 env['GCLIENT_URL'] = url
886 subprocess.Popen(args, cwd=dir, env=env).communicate()
887
888
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000889@attr('usage', '[url] [safesync url]')
890def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000891 """Create a .gclient file in the current directory.
892
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000893This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000894top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000895modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000896provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000897URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000898"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000899 parser.add_option('--spec',
900 help='create a gclient file containing the provided '
901 'string. Due to Cygwin/Python brokenness, it '
902 'probably can\'t contain any newlines.')
903 parser.add_option('--name',
904 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000905 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000906 if ((options.spec and args) or len(args) > 2 or
907 (not options.spec and not args)):
908 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
909
maruel@chromium.org0329e672009-05-13 18:41:04 +0000910 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000911 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000912 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000913 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000914 if options.spec:
915 client.SetConfig(options.spec)
916 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000917 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000918 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000919 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000920 else:
921 # specify an alternate relpath for the given URL.
922 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000923 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000924 if len(args) > 1:
925 safesync_url = args[1]
926 client.SetDefaultConfig(name, base_url, safesync_url)
927 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000928 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000929
930
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000931def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000932 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000933 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
934 help='override deps for the specified (comma-separated) '
935 'platform(s); \'all\' will process all deps_os '
936 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000937 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000938 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000939 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000940 client = GClient.LoadCurrentConfig(options)
941
942 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000943 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000944
945 if options.verbose:
946 # Print out the .gclient file. This is longer than if we just printed the
947 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000948 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000949 return client.RunOnDeps('export', args)
950
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000951
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000952@attr('epilog', """Example:
953 gclient pack > patch.txt
954 generate simple patch for configured client and dependences
955""")
956def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000957 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000958
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000959Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000960dependencies, and performs minimal postprocessing of the output. The
961resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000962checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000963"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000964 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
965 help='override deps for the specified (comma-separated) '
966 'platform(s); \'all\' will process all deps_os '
967 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000968 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000969 client = GClient.LoadCurrentConfig(options)
970 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000971 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000972 if options.verbose:
973 # Print out the .gclient file. This is longer than if we just printed the
974 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000975 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000976 return client.RunOnDeps('pack', args)
977
978
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000979def CMDstatus(parser, args):
980 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000981 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
982 help='override deps for the specified (comma-separated) '
983 'platform(s); \'all\' will process all deps_os '
984 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000985 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000986 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000987 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000988 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000989 if options.verbose:
990 # Print out the .gclient file. This is longer than if we just printed the
991 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000992 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000993 return client.RunOnDeps('status', args)
994
995
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000996@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000997 gclient sync
998 update files from SCM according to current configuration,
999 *for modules which have changed since last update or sync*
1000 gclient sync --force
1001 update files from SCM according to current configuration, for
1002 all modules (useful for recovering files deleted from local copy)
1003 gclient sync --revision src@31000
1004 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001005""")
1006def CMDsync(parser, args):
1007 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001008 parser.add_option('-f', '--force', action='store_true',
1009 help='force update even for unchanged modules')
1010 parser.add_option('-n', '--nohooks', action='store_true',
1011 help='don\'t run hooks after the update is complete')
1012 parser.add_option('-r', '--revision', action='append',
1013 dest='revisions', metavar='REV', default=[],
1014 help='Enforces revision/hash for the solutions with the '
1015 'format src@rev. The src@ part is optional and can be '
1016 'skipped. -r can be used multiple times when .gclient '
1017 'has multiple solutions configured and will work even '
1018 'if the src@ part is skipped.')
1019 parser.add_option('-H', '--head', action='store_true',
1020 help='skips any safesync_urls specified in '
1021 'configured solutions and sync to head instead')
1022 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1023 help='delete any unexpected unversioned trees '
1024 'that are in the checkout')
1025 parser.add_option('-R', '--reset', action='store_true',
1026 help='resets any local changes before updating (git only)')
1027 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1028 help='override deps for the specified (comma-separated) '
1029 'platform(s); \'all\' will process all deps_os '
1030 'references')
1031 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1032 help='Skip svn up whenever possible by requesting '
1033 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001034 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001035 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001036
1037 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001038 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039
maruel@chromium.org307d1792010-05-31 20:03:13 +00001040 if options.revisions and options.head:
1041 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001042 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001043
1044 if options.verbose:
1045 # Print out the .gclient file. This is longer than if we just printed the
1046 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001047 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001048 return client.RunOnDeps('update', args)
1049
1050
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001051def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001052 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001053 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001054
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001055def CMDdiff(parser, args):
1056 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001057 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')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001061 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001062 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001063 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001064 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001065 if options.verbose:
1066 # Print out the .gclient file. This is longer than if we just printed the
1067 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001068 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001069 return client.RunOnDeps('diff', args)
1070
1071
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001072def CMDrevert(parser, args):
1073 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001074 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1075 help='override deps for the specified (comma-separated) '
1076 'platform(s); \'all\' will process all deps_os '
1077 'references')
1078 parser.add_option('-n', '--nohooks', action='store_true',
1079 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001080 (options, args) = parser.parse_args(args)
1081 # --force is implied.
1082 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001083 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001084 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001085 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001086 return client.RunOnDeps('revert', args)
1087
1088
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001089def CMDrunhooks(parser, args):
1090 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001091 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1092 help='override deps for the specified (comma-separated) '
1093 'platform(s); \'all\' will process all deps_os '
1094 'references')
1095 parser.add_option('-f', '--force', action='store_true', default=True,
1096 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001097 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001098 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001099 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001100 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001101 if options.verbose:
1102 # Print out the .gclient file. This is longer than if we just printed the
1103 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001104 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001105 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001106 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001107 return client.RunOnDeps('runhooks', args)
1108
1109
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001110def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001111 """Output revision info mapping for the client and its dependencies.
1112
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001113 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001114 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001115 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1116 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001117 commit can change.
1118 """
1119 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1120 help='override deps for the specified (comma-separated) '
1121 'platform(s); \'all\' will process all deps_os '
1122 'references')
1123 parser.add_option('-s', '--snapshot', action='store_true',
1124 help='creates a snapshot .gclient file of the current '
1125 'version of all repositories to reproduce the tree, '
1126 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001127 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001128 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001130 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001132 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001133
1134
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001135def Command(name):
1136 return getattr(sys.modules[__name__], 'CMD' + name, None)
1137
1138
1139def CMDhelp(parser, args):
1140 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001141 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001142 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001143 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001144 parser.print_help()
1145 return 0
1146
1147
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001148def GenUsage(parser, command):
1149 """Modify an OptParse object with the function's documentation."""
1150 obj = Command(command)
1151 if command == 'help':
1152 command = '<command>'
1153 # OptParser.description prefer nicely non-formatted strings.
1154 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1155 usage = getattr(obj, 'usage', '')
1156 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1157 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001158
1159
1160def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001161 """Doesn't parse the arguments here, just find the right subcommand to
1162 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001163 try:
1164 # Do it late so all commands are listed.
1165 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1166 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1167 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1168 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001169 parser.add_option('-v', '--verbose', action='count', default=0,
1170 help='Produces additional output for diagnostics. Can be '
1171 'used up to three times for more logging info.')
1172 parser.add_option('--gclientfile', dest='config_filename',
1173 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1174 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001175 # Integrate standard options processing.
1176 old_parser = parser.parse_args
1177 def Parse(args):
1178 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001179 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001180 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001181 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001182 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001183 level = logging.DEBUG
1184 logging.basicConfig(level=level,
1185 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1186 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001187 if not hasattr(options, 'revisions'):
1188 # GClient.RunOnDeps expects it even if not applicable.
1189 options.revisions = []
1190 if not hasattr(options, 'head'):
1191 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001192 if not hasattr(options, 'nohooks'):
1193 options.nohooks = True
1194 if not hasattr(options, 'deps_os'):
1195 options.deps_os = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001196 return (options, args)
1197 parser.parse_args = Parse
1198 # We don't want wordwrapping in epilog (usually examples)
1199 parser.format_epilog = lambda _: parser.epilog or ''
1200 if argv:
1201 command = Command(argv[0])
1202 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001203 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001204 GenUsage(parser, argv[0])
1205 return command(parser, argv[1:])
1206 # Not a known command. Default to help.
1207 GenUsage(parser, 'help')
1208 return CMDhelp(parser, argv)
1209 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001210 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001211 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001212
1213
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001214if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001215 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001216
1217# vim: ts=2:sw=2:tw=80:et: