blob: a50935daa332bab657f5130deb0c6e08a919072d [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
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urllib
63
maruel@chromium.orgada4c652009-12-03 15:32:01 +000064import breakpad
65
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000066import gclient_scm
67import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000068from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000069
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000071def 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
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000077
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079## GClient implementation.
80
81
maruel@chromium.org116704f2010-06-11 17:34:38 +000082class 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
99 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
100 """Resolve the URL for this From entry."""
101 sub_deps_target_name = target_name
102 if self.sub_target_name:
103 sub_deps_target_name = self.sub_target_name
104 url = sub_deps[sub_deps_target_name]
105 if url.startswith('/'):
106 # If it's a relative URL, we need to resolve the URL relative to the
107 # sub deps base URL.
108 if not isinstance(sub_deps_base_url, basestring):
109 sub_deps_base_url = sub_deps_base_url.GetPath()
110 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
111 None)
112 url = scm.FullUrlForRelativeUrl(url)
113 return url
114
115 class FileImpl(object):
116 """Used to implement the File('') syntax which lets you sync a single file
117 from an SVN repo."""
118
119 def __init__(self, file_location):
120 self.file_location = file_location
121
122 def __str__(self):
123 return 'File("%s")' % self.file_location
124
125 def GetPath(self):
126 return os.path.split(self.file_location)[0]
127
128 def GetFilename(self):
129 rev_tokens = self.file_location.split('@')
130 return os.path.split(rev_tokens[0])[1]
131
132 def GetRevision(self):
133 rev_tokens = self.file_location.split('@')
134 if len(rev_tokens) > 1:
135 return rev_tokens[1]
136 return None
137
138 class VarImpl(object):
139 def __init__(self, custom_vars, local_scope):
140 self._custom_vars = custom_vars
141 self._local_scope = local_scope
142
143 def Lookup(self, var_name):
144 """Implements the Var syntax."""
145 if var_name in self._custom_vars:
146 return self._custom_vars[var_name]
147 elif var_name in self._local_scope.get("vars", {}):
148 return self._local_scope["vars"][var_name]
149 raise gclient_utils.Error("Var is not defined: %s" % var_name)
150
151
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000152class Dependency(GClientKeywords):
153 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000154 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000155
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000156 def __init__(self, parent, name, url, safesync_url=None, custom_deps=None,
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000157 custom_vars=None, deps_file=None):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000158 GClientKeywords.__init__(self)
159 self.parent = parent
160 self.name = name
161 self.url = url
162 # These 2 are only set in .gclient and not in DEPS files.
163 self.safesync_url = safesync_url
164 self.custom_vars = custom_vars or {}
165 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000166 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000167 self.dependencies = []
168 self.deps_file = deps_file or self.DEPS_FILE
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000169
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000170 # Sanity checks
171 if not self.name and self.parent:
172 raise gclient_utils.Error('Dependency without name')
173 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
182
183class GClient(Dependency):
184 """Main gclient checkout root where .gclient resides."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000185 SUPPORTED_COMMANDS = [
kbr@google.comab318592009-09-04 00:54:55 +0000186 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
187 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000188 ]
189
maruel@chromium.org116704f2010-06-11 17:34:38 +0000190 DEPS_OS_CHOICES = {
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000191 "win32": "win",
192 "win": "win",
193 "cygwin": "win",
194 "darwin": "mac",
195 "mac": "mac",
196 "unix": "unix",
197 "linux": "unix",
198 "linux2": "unix",
199 }
200
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000201 DEFAULT_CLIENT_FILE_TEXT = ("""\
202solutions = [
203 { "name" : "%(solution_name)s",
204 "url" : "%(solution_url)s",
205 "custom_deps" : {
206 },
207 "safesync_url": "%(safesync_url)s"
208 },
209]
210""")
211
212 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
213 { "name" : "%(solution_name)s",
214 "url" : "%(solution_url)s",
215 "custom_deps" : {
216 %(solution_deps)s,
217 },
218 "safesync_url": "%(safesync_url)s"
219 },
220""")
221
222 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
223# Snapshot generated with gclient revinfo --snapshot
224solutions = [
225%(solution_list)s
226]
227""")
228
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000229 def __init__(self, root_dir, options):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000230 Dependency.__init__(self, None, None, None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000231 self._root_dir = root_dir
232 self._options = options
maruel@chromium.org116704f2010-06-11 17:34:38 +0000233 self.config_content = None
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000234
235 def SetConfig(self, content):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000236 assert self.dependencies == []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000237 config_dict = {}
maruel@chromium.org116704f2010-06-11 17:34:38 +0000238 self.config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000239 try:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000240 exec(content, config_dict)
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000241 except SyntaxError, e:
242 try:
243 # Try to construct a human readable error message
244 error_message = [
245 'There is a syntax error in your configuration file.',
246 'Line #%s, character %s:' % (e.lineno, e.offset),
247 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
248 except:
249 # Something went wrong, re-raise the original exception
250 raise e
251 else:
252 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000253 raise gclient_utils.Error('\n'.join(error_message))
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000254 for s in config_dict.get('solutions', []):
255 self.dependencies.append(Dependency(
256 self, s['name'], s['url'],
257 s.get('safesync_url', None),
258 s.get('custom_deps', {}),
259 s.get('custom_vars', {})))
260 # .gclient can have hooks.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000261 self.deps_hooks = config_dict.get('hooks', [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000262
263 def SaveConfig(self):
maruel@chromium.org75a59272010-06-11 22:34:03 +0000264 gclient_utils.FileWrite(os.path.join(self.root_dir(),
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000265 self._options.config_filename),
maruel@chromium.org116704f2010-06-11 17:34:38 +0000266 self.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000267
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000268 @staticmethod
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000269 def LoadCurrentConfig(options):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000270 """Searches for and loads a .gclient file relative to the current working
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000271 dir. Returns a GClient object."""
272 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
273 if not path:
274 return None
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000275 client = GClient(path, options)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000276 client.SetConfig(gclient_utils.FileRead(
277 os.path.join(path, options.config_filename)))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000278 return client
279
280 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000281 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000282 'solution_name': solution_name,
283 'solution_url': solution_url,
284 'safesync_url' : safesync_url,
285 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000286
287 def _SaveEntries(self, entries):
288 """Creates a .gclient_entries file to record the list of unique checkouts.
289
290 The .gclient_entries file lives in the same directory as .gclient.
291
292 Args:
293 entries: A sequence of solution names.
294 """
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000295 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
296 # makes testing a bit too fun.
297 result = pprint.pformat(entries, 2)
298 if result.startswith('{\''):
maruel@chromium.org1edec4d2010-04-08 17:17:06 +0000299 result = '{ \'' + result[2:]
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000300 text = "entries = \\\n" + result + '\n'
maruel@chromium.org75a59272010-06-11 22:34:03 +0000301 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000302 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000303
304 def _ReadEntries(self):
305 """Read the .gclient_entries file for the given client.
306
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000307 Returns:
308 A sequence of solution names, which will be empty if there is the
309 entries file hasn't been created yet.
310 """
311 scope = {}
maruel@chromium.org75a59272010-06-11 22:34:03 +0000312 filename = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000313 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000314 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000315 exec(gclient_utils.FileRead(filename), scope)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000316 return scope['entries']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000317
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000318 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000319 custom_vars, parse_hooks):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000320 """Parses the DEPS file for the specified solution.
321
322 Args:
323 solution_name: The name of the solution to query.
324 solution_deps_content: Content of the DEPS file for the solution
325 custom_vars: A dict of vars to override any vars defined in the DEPS file.
326
327 Returns:
328 A dict mapping module names (as relative paths) to URLs or an empty
329 dict if the solution does not have a DEPS file.
330 """
331 # Skip empty
332 if not solution_deps_content:
333 return {}
334 # Eval the content
335 local_scope = {}
maruel@chromium.org116704f2010-06-11 17:34:38 +0000336 var = self.VarImpl(custom_vars, local_scope)
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000337 global_scope = {
338 "File": self.FileImpl,
339 "From": self.FromImpl,
340 "Var": var.Lookup,
341 "deps_os": {},
342 }
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000343 exec(solution_deps_content, global_scope, local_scope)
344 deps = local_scope.get("deps", {})
345
346 # load os specific dependencies if defined. these dependencies may
347 # override or extend the values defined by the 'deps' member.
348 if "deps_os" in local_scope:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000349 if self._options.deps_os is not None:
350 deps_to_include = self._options.deps_os.split(",")
351 if "all" in deps_to_include:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000352 deps_to_include = list(set(self.DEPS_OS_CHOICES.itervalues()))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000353 else:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000354 deps_to_include = [self.DEPS_OS_CHOICES.get(sys.platform, "unix")]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000355
356 deps_to_include = set(deps_to_include)
357 for deps_os_key in deps_to_include:
358 os_deps = local_scope["deps_os"].get(deps_os_key, {})
359 if len(deps_to_include) > 1:
360 # Ignore any overrides when including deps for more than one
361 # platform, so we collect the broadest set of dependencies available.
362 # We may end up with the wrong revision of something for our
363 # platform, but this is the best we can do.
364 deps.update([x for x in os_deps.items() if not x[0] in deps])
365 else:
366 deps.update(os_deps)
367
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000368 if 'hooks' in local_scope and parse_hooks:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000369 # TODO(maruel): Temporary Hack. Since this function is misplaced, find the
370 # right 'self' to add the hooks.
371 for d in self.dependencies:
372 if d.name == solution_name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000373 d.deps_hooks.extend(local_scope['hooks'])
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000374 break
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000375
376 # If use_relative_paths is set in the DEPS file, regenerate
377 # the dictionary using paths relative to the directory containing
378 # the DEPS file.
379 if local_scope.get('use_relative_paths'):
380 rel_deps = {}
381 for d, url in deps.items():
382 # normpath is required to allow DEPS to use .. in their
383 # dependency local path.
384 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
385 return rel_deps
386 else:
387 return deps
388
389 def _ParseAllDeps(self, solution_urls, solution_deps_content):
390 """Parse the complete list of dependencies for the client.
391
392 Args:
393 solution_urls: A dict mapping module names (as relative paths) to URLs
394 corresponding to the solutions specified by the client. This parameter
395 is passed as an optimization.
396 solution_deps_content: A dict mapping module names to the content
397 of their DEPS files
398
399 Returns:
400 A dict mapping module names (as relative paths) to URLs corresponding
401 to the entire set of dependencies to checkout for the given client.
402
403 Raises:
404 Error: If a dependency conflicts with another dependency or of a solution.
405 """
406 deps = {}
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000407 for solution in self.dependencies:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000408 solution_deps = self._ParseSolutionDeps(
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000409 solution.name,
410 solution_deps_content[solution.name],
411 solution.custom_vars,
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000412 True)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000413
414 # If a line is in custom_deps, but not in the solution, we want to append
415 # this line to the solution.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000416 for d in solution.custom_deps:
417 if d not in solution_deps:
418 solution_deps[d] = solution.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000419
420 for d in solution_deps:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000421 if d in solution.custom_deps:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000422 # Dependency is overriden.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000423 url = solution.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000424 if url is None:
425 continue
426 else:
427 url = solution_deps[d]
428 # if we have a From reference dependent on another solution, then
429 # just skip the From reference. When we pull deps for the solution,
430 # we will take care of this dependency.
431 #
432 # If multiple solutions all have the same From reference, then we
433 # should only add one to our list of dependencies.
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000434 if isinstance(url, self.FromImpl):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000435 if url.module_name in solution_urls:
436 # Already parsed.
437 continue
438 if d in deps and type(deps[d]) != str:
439 if url.module_name == deps[d].module_name:
440 continue
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000441 elif isinstance(url, str):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000442 parsed_url = urlparse.urlparse(url)
443 scheme = parsed_url[0]
444 if not scheme:
445 # A relative url. Fetch the real base.
446 path = parsed_url[2]
447 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000448 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000449 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000450 # Create a scm just to query the full url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000451 scm = gclient_scm.CreateSCM(solution.url, self.root_dir(),
452 None)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000453 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000454 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000455 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000456 "Solutions have conflicting versions of dependency \"%s\"" % d)
457 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000458 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000459 "Dependency \"%s\" conflicts with specified solution" % d)
460 # Grab the dependency.
461 deps[d] = url
462 return deps
463
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000464 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000465 """Runs the action from a single hook.
466 """
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000467 logging.info(hook_dict)
468 logging.info(matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000469 command = hook_dict['action'][:]
470 if command[0] == 'python':
471 # If the hook specified "python" as the first item, the action is a
472 # Python script. Run it by starting a new copy of the same
473 # interpreter.
474 command[0] = sys.executable
475
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000476 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000477 splice_index = command.index('$matching_files')
478 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000479
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000480 # Use a discrete exit status code of 2 to indicate that a hook action
481 # failed. Users of this script may wish to treat hook action failures
482 # differently from VC failures.
maruel@chromium.org75a59272010-06-11 22:34:03 +0000483 gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000484
485 def _RunHooks(self, command, file_list, is_using_git):
486 """Evaluates all hooks, running actions as needed.
487 """
488 # Hooks only run for these command types.
489 if not command in ('update', 'revert', 'runhooks'):
490 return
491
evan@chromium.org67820ef2009-07-27 17:23:00 +0000492 # Hooks only run when --nohooks is not specified
493 if self._options.nohooks:
494 return
495
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000496 # Get any hooks from the .gclient file.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000497 hooks = self.deps_hooks[:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000498 # Add any hooks found in DEPS files.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000499 for d in self.dependencies:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000500 hooks.extend(d.deps_hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000501
502 # If "--force" was specified, run all hooks regardless of what files have
503 # changed. If the user is using git, then we don't know what files have
504 # changed so we always run all hooks.
505 if self._options.force or is_using_git:
506 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000507 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000508 return
509
510 # Run hooks on the basis of whether the files from the gclient operation
511 # match each hook's pattern.
512 for hook_dict in hooks:
513 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000514 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000515 if matching_file_list:
516 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000517
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000518 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000519 """Checks for revision overrides."""
520 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000521 if self._options.head:
522 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000523 for s in self.dependencies:
524 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000525 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000526 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000527 rev = handle.read().strip()
528 handle.close()
529 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000530 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000531 if not self._options.revisions:
532 return revision_overrides
533 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000534 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000535 index = 0
536 for revision in self._options.revisions:
537 if not '@' in revision:
538 # Support for --revision 123
539 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000540 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000541 if not sol in solutions_names:
542 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
543 print >> sys.stderr, ('Please fix your script, having invalid '
544 '--revision flags will soon considered an error.')
545 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000546 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000547 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000548 return revision_overrides
549
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000550 def RunOnDeps(self, command, args):
551 """Runs a command on each dependency in a client and its dependencies.
552
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000553 Args:
554 command: The command to use (e.g., 'status' or 'diff')
555 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000556 """
maruel@chromium.org116704f2010-06-11 17:34:38 +0000557 if not command in self.SUPPORTED_COMMANDS:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000558 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000559
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000560 if not self.dependencies:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000561 raise gclient_utils.Error("No solution specified")
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000562 revision_overrides = self._EnforceRevisions()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000563
564 # When running runhooks --force, there's no need to consult the SCM.
565 # All known hooks are expected to run unconditionally regardless of working
566 # copy state, so skip the SCM status check.
567 run_scm = not (command == 'runhooks' and self._options.force)
568
569 entries = {}
570 entries_deps_content = {}
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000571 file_list = []
572 # Run on the base solutions first.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000573 for solution in self.dependencies:
574 name = solution.name
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000575 if name in entries:
576 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000577 url = solution.url
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000578 entries[name] = url
579 if run_scm and url:
580 self._options.revision = revision_overrides.get(name)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000581 scm = gclient_scm.CreateSCM(url, self.root_dir(), name)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000582 scm.RunCommand(command, self._options, args, file_list)
583 file_list = [os.path.join(name, f.strip()) for f in file_list]
584 self._options.revision = None
585 try:
586 deps_content = gclient_utils.FileRead(
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000587 os.path.join(self.root_dir(), name, solution.deps_file))
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000588 except IOError, e:
589 if e.errno != errno.ENOENT:
590 raise
591 deps_content = ""
592 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000593
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000594 # Process the dependencies next (sort alphanumerically to ensure that
595 # containing directories get populated first and for readability)
596 deps = self._ParseAllDeps(entries, entries_deps_content)
597 deps_to_process = deps.keys()
598 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000599
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000600 # First pass for direct dependencies.
601 if command == 'update' and not self._options.verbose:
602 pm = Progress('Syncing projects', len(deps_to_process))
603 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000604 if command == 'update' and not self._options.verbose:
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000605 pm.update()
606 if type(deps[d]) == str:
607 url = deps[d]
608 entries[d] = url
609 if run_scm:
610 self._options.revision = revision_overrides.get(d)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000611 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000612 scm.RunCommand(command, self._options, args, file_list)
613 self._options.revision = None
614 elif isinstance(deps[d], self.FileImpl):
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000615 file_dep = deps[d]
616 self._options.revision = file_dep.GetRevision()
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000617 if run_scm:
maruel@chromium.org75a59272010-06-11 22:34:03 +0000618 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000619 scm.RunCommand("updatesingle", self._options,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000620 args + [file_dep.GetFilename()], file_list)
maruel@chromium.org79692d62010-05-14 18:57:13 +0000621
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000622 if command == 'update' and not self._options.verbose:
623 pm.end()
piman@chromium.org6f363722010-04-27 00:41:09 +0000624
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000625 # Second pass for inherited deps (via the From keyword)
626 for d in deps_to_process:
627 if isinstance(deps[d], self.FromImpl):
maruel@chromium.org75a59272010-06-11 22:34:03 +0000628 filename = os.path.join(self.root_dir(),
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000629 deps[d].module_name,
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000630 self.DEPS_FILE)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000631 content = gclient_utils.FileRead(filename)
632 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
633 False)
634 # Getting the URL from the sub_deps file can involve having to resolve
635 # a File() or having to resolve a relative URL. To resolve relative
636 # URLs, we need to pass in the orignal sub deps URL.
637 sub_deps_base_url = deps[deps[d].module_name]
maruel@chromium.org75a59272010-06-11 22:34:03 +0000638 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000639 entries[d] = url
640 if run_scm:
641 self._options.revision = revision_overrides.get(d)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000642 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000643 scm.RunCommand(command, self._options, args, file_list)
644 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000645
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000646 # Convert all absolute paths to relative.
647 for i in range(len(file_list)):
648 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
649 # It depends on the command being executed (like runhooks vs sync).
650 if not os.path.isabs(file_list[i]):
651 continue
652
maruel@chromium.org75a59272010-06-11 22:34:03 +0000653 prefix = os.path.commonprefix([self.root_dir().lower(),
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000654 file_list[i].lower()])
655 file_list[i] = file_list[i][len(prefix):]
656
657 # Strip any leading path separators.
658 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
659 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000660
maruel@chromium.org75a59272010-06-11 22:34:03 +0000661 is_using_git = gclient_utils.IsUsingGit(self.root_dir(), entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000662 self._RunHooks(command, file_list, is_using_git)
663
664 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000665 # Notify the user if there is an orphaned entry in their working copy.
666 # Only delete the directory if there are no changes in it, and
667 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668 prev_entries = self._ReadEntries()
669 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000670 # Fix path separator on Windows.
671 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000672 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000673 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000674 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000675 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000676 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000677 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000678 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000679 else:
680 file_list = []
maruel@chromium.org75a59272010-06-11 22:34:03 +0000681 scm = gclient_scm.CreateSCM(prev_entries[entry], self.root_dir(),
msb@chromium.org83017012009-09-28 18:52:12 +0000682 entry_fixed)
683 scm.status(self._options, [], file_list)
684 modified_files = file_list != []
685 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000686 # There are modified files in this entry. Keep warning until
687 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000688 print(("\nWARNING: \"%s\" is no longer part of this client. "
689 "It is recommended that you manually remove it.\n") %
690 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000691 else:
692 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000693 print("\n________ deleting \'%s\' " +
maruel@chromium.org75a59272010-06-11 22:34:03 +0000694 "in \'%s\'") % (entry_fixed, self.root_dir())
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000695 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000696 # record the current list of entries for next time
697 self._SaveEntries(entries)
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000698 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000699
700 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000701 """Output revision info mapping for the client and its dependencies.
702
703 This allows the capture of an overall "revision" for the source tree that
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000704 can be used to reproduce the same tree in the future. It is only useful for
705 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
706 number or a git hash. A git branch name isn't "pinned" since the actual
707 commit can change.
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000708
709 The --snapshot option allows creating a .gclient file to reproduce the tree.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000710 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000711 if not self.dependencies:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000712 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000713
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000714 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000715 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000716 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000717 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000718 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000719
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000720 # text of the snapshot gclient file
721 new_gclient = ""
722 # Dictionary of { path : SCM url } to ensure no duplicate solutions
723 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000724 entries = {}
725 entries_deps_content = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000726 # Run on the base solutions first.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000727 for solution in self.dependencies:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000728 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000729 name = solution.name
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000730 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000731 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000732 (url, rev) = GetURLAndRev(name, solution.url)
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000733 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000734 solution_names[name] = "%s@%s" % (url, rev)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000735 deps_file = solution.deps_file
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000736 if '/' in deps_file or '\\' in deps_file:
737 raise gclient_utils.Error('deps_file name must not be a path, just a '
738 'filename.')
739 try:
740 deps_content = gclient_utils.FileRead(
maruel@chromium.org75a59272010-06-11 22:34:03 +0000741 os.path.join(self.root_dir(), name, deps_file))
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000742 except IOError, e:
743 if e.errno != errno.ENOENT:
744 raise
745 deps_content = ""
746 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000747
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000748 # Process the dependencies next (sort alphanumerically to ensure that
749 # containing directories get populated first and for readability)
750 deps = self._ParseAllDeps(entries, entries_deps_content)
751 deps_to_process = deps.keys()
752 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000753
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000754 # First pass for direct dependencies.
755 for d in deps_to_process:
756 if type(deps[d]) == str:
757 (url, rev) = GetURLAndRev(d, deps[d])
758 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000759
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000760 # Second pass for inherited deps (via the From keyword)
761 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000762 if isinstance(deps[d], self.FromImpl):
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000763 deps_parent_url = entries[deps[d].module_name]
764 if deps_parent_url.find("@") < 0:
765 raise gclient_utils.Error("From %s missing revisioned url" %
766 deps[d].module_name)
767 content = gclient_utils.FileRead(os.path.join(
maruel@chromium.org75a59272010-06-11 22:34:03 +0000768 self.root_dir(),
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000769 deps[d].module_name,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000770 self.DEPS_FILE))
771 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
772 False)
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000773 (url, rev) = GetURLAndRev(d, sub_deps[d])
774 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000775
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000776 # Build the snapshot configuration string
777 if self._options.snapshot:
778 url = entries.pop(name)
779 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
780 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000781
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000782 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000783 'solution_name': name,
784 'solution_url': url,
785 'safesync_url' : "",
786 'solution_deps': custom_deps,
787 }
788 else:
789 print(";\n".join(["%s: %s" % (x, entries[x])
790 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000791
792 # Print the snapshot configuration file
793 if self._options.snapshot:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000794 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
maruel@chromium.org75a59272010-06-11 22:34:03 +0000795 snapclient = GClient(self.root_dir(), self._options)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000796 snapclient.SetConfig(config)
maruel@chromium.org116704f2010-06-11 17:34:38 +0000797 print(snapclient.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000798
maruel@chromium.org75a59272010-06-11 22:34:03 +0000799 def root_dir(self):
800 return self._root_dir
801
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000802
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000803#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000804
805
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000806def CMDcleanup(parser, args):
807 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000808
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000809Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000810"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000811 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
812 help='override deps for the specified (comma-separated) '
813 'platform(s); \'all\' will process all deps_os '
814 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000815 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000816 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000817 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000818 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000819 if options.verbose:
820 # Print out the .gclient file. This is longer than if we just printed the
821 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000822 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823 return client.RunOnDeps('cleanup', args)
824
825
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000826@attr('usage', '[url] [safesync url]')
827def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000828 """Create a .gclient file in the current directory.
829
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000830This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000831top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000832modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000833provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000834URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000835"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000836 parser.add_option('--spec',
837 help='create a gclient file containing the provided '
838 'string. Due to Cygwin/Python brokenness, it '
839 'probably can\'t contain any newlines.')
840 parser.add_option('--name',
841 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000842 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000843 if ((options.spec and args) or len(args) > 2 or
844 (not options.spec and not args)):
845 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
846
maruel@chromium.org0329e672009-05-13 18:41:04 +0000847 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000848 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000849 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000850 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000851 if options.spec:
852 client.SetConfig(options.spec)
853 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000854 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000855 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000856 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000857 else:
858 # specify an alternate relpath for the given URL.
859 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000860 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000861 if len(args) > 1:
862 safesync_url = args[1]
863 client.SetDefaultConfig(name, base_url, safesync_url)
864 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000865 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000866
867
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000868def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000869 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000870 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
871 help='override deps for the specified (comma-separated) '
872 'platform(s); \'all\' will process all deps_os '
873 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000874 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000875 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000876 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000877 client = GClient.LoadCurrentConfig(options)
878
879 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000880 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000881
882 if options.verbose:
883 # Print out the .gclient file. This is longer than if we just printed the
884 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000885 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000886 return client.RunOnDeps('export', args)
887
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000888
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000889@attr('epilog', """Example:
890 gclient pack > patch.txt
891 generate simple patch for configured client and dependences
892""")
893def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000894 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000895
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000896Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000897dependencies, and performs minimal postprocessing of the output. The
898resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000899checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000900"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000901 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
902 help='override deps for the specified (comma-separated) '
903 'platform(s); \'all\' will process all deps_os '
904 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000905 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000906 client = GClient.LoadCurrentConfig(options)
907 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000908 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000909 if options.verbose:
910 # Print out the .gclient file. This is longer than if we just printed the
911 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000912 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000913 return client.RunOnDeps('pack', args)
914
915
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000916def CMDstatus(parser, args):
917 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000918 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
919 help='override deps for the specified (comma-separated) '
920 'platform(s); \'all\' will process all deps_os '
921 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000922 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000923 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000924 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000925 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000926 if options.verbose:
927 # Print out the .gclient file. This is longer than if we just printed the
928 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000929 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930 return client.RunOnDeps('status', args)
931
932
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000933@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000934 gclient sync
935 update files from SCM according to current configuration,
936 *for modules which have changed since last update or sync*
937 gclient sync --force
938 update files from SCM according to current configuration, for
939 all modules (useful for recovering files deleted from local copy)
940 gclient sync --revision src@31000
941 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000942""")
943def CMDsync(parser, args):
944 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000945 parser.add_option('-f', '--force', action='store_true',
946 help='force update even for unchanged modules')
947 parser.add_option('-n', '--nohooks', action='store_true',
948 help='don\'t run hooks after the update is complete')
949 parser.add_option('-r', '--revision', action='append',
950 dest='revisions', metavar='REV', default=[],
951 help='Enforces revision/hash for the solutions with the '
952 'format src@rev. The src@ part is optional and can be '
953 'skipped. -r can be used multiple times when .gclient '
954 'has multiple solutions configured and will work even '
955 'if the src@ part is skipped.')
956 parser.add_option('-H', '--head', action='store_true',
957 help='skips any safesync_urls specified in '
958 'configured solutions and sync to head instead')
959 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
960 help='delete any unexpected unversioned trees '
961 'that are in the checkout')
962 parser.add_option('-R', '--reset', action='store_true',
963 help='resets any local changes before updating (git only)')
964 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')
968 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
969 help='Skip svn up whenever possible by requesting '
970 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000971 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000972 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000973
974 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000975 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000976
maruel@chromium.org307d1792010-05-31 20:03:13 +0000977 if options.revisions and options.head:
978 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000979 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000980
981 if options.verbose:
982 # Print out the .gclient file. This is longer than if we just printed the
983 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000984 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000985 return client.RunOnDeps('update', args)
986
987
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000988def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000989 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000990 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000991
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000992def CMDdiff(parser, args):
993 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000994 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
995 help='override deps for the specified (comma-separated) '
996 'platform(s); \'all\' will process all deps_os '
997 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000998 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000999 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001000 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001001 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001002 if options.verbose:
1003 # Print out the .gclient file. This is longer than if we just printed the
1004 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001005 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001006 return client.RunOnDeps('diff', args)
1007
1008
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001009def CMDrevert(parser, args):
1010 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001011 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1012 help='override deps for the specified (comma-separated) '
1013 'platform(s); \'all\' will process all deps_os '
1014 'references')
1015 parser.add_option('-n', '--nohooks', action='store_true',
1016 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001017 (options, args) = parser.parse_args(args)
1018 # --force is implied.
1019 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001020 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001021 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001022 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001023 return client.RunOnDeps('revert', args)
1024
1025
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001026def CMDrunhooks(parser, args):
1027 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001028 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1029 help='override deps for the specified (comma-separated) '
1030 'platform(s); \'all\' will process all deps_os '
1031 'references')
1032 parser.add_option('-f', '--force', action='store_true', default=True,
1033 help='Deprecated. No effect.')
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 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001037 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001038 if options.verbose:
1039 # Print out the .gclient file. This is longer than if we just printed the
1040 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001041 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001042 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001043 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044 return client.RunOnDeps('runhooks', args)
1045
1046
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001047def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001048 """Output revision info mapping for the client and its dependencies.
1049
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001050 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001051 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001052 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1053 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001054 commit can change.
1055 """
1056 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1057 help='override deps for the specified (comma-separated) '
1058 'platform(s); \'all\' will process all deps_os '
1059 'references')
1060 parser.add_option('-s', '--snapshot', action='store_true',
1061 help='creates a snapshot .gclient file of the current '
1062 'version of all repositories to reproduce the tree, '
1063 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001064 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001065 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001066 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001067 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001068 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001069 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001070
1071
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001072def 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."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001078 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001079 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001080 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001081 parser.print_help()
1082 return 0
1083
1084
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001085def 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)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001095
1096
1097def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001098 """Doesn't parse the arguments here, just find the right subcommand to
1099 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001100 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__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001106 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')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001112 # Integrate standard options processing.
1113 old_parser = parser.parse_args
1114 def Parse(args):
1115 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001116 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001117 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001118 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001119 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001120 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'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001124 if not hasattr(options, 'revisions'):
1125 # GClient.RunOnDeps expects it even if not applicable.
1126 options.revisions = []
1127 if not hasattr(options, 'head'):
1128 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001129 if not hasattr(options, 'nohooks'):
1130 options.nohooks = True
1131 if not hasattr(options, 'deps_os'):
1132 options.deps_os = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001133 return (options, args)
1134 parser.parse_args = Parse
1135 # We don't want wordwrapping in epilog (usually examples)
1136 parser.format_epilog = lambda _: parser.epilog or ''
1137 if argv:
1138 command = Command(argv[0])
1139 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001140 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001141 GenUsage(parser, argv[0])
1142 return command(parser, argv[1:])
1143 # Not a known command. Default to help.
1144 GenUsage(parser, 'help')
1145 return CMDhelp(parser, argv)
1146 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001147 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001148 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001149
1150
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001151if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001152 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001153
1154# vim: ts=2:sw=2:tw=80:et: