blob: a3ff76c81fa4f108f6ffdd571c4a822fe28362c2 [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
407 DEPS_OS_CHOICES = {
408 "win32": "win",
409 "win": "win",
410 "cygwin": "win",
411 "darwin": "mac",
412 "mac": "mac",
413 "unix": "unix",
414 "linux": "unix",
415 "linux2": "unix",
416 }
417
418 DEFAULT_CLIENT_FILE_TEXT = ("""\
419solutions = [
420 { "name" : "%(solution_name)s",
421 "url" : "%(solution_url)s",
422 "custom_deps" : {
423 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000424 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000425 },
426]
427""")
428
429 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
430 { "name" : "%(solution_name)s",
431 "url" : "%(solution_url)s",
432 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000433%(solution_deps)s },
434 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000435 },
436""")
437
438 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
439# Snapshot generated with gclient revinfo --snapshot
440solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000441%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000442""")
443
444 def __init__(self, root_dir, options):
445 Dependency.__init__(self, None, None, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000446 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000447 if options.deps_os:
448 enforced_os = options.deps_os.split(',')
449 else:
450 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
451 if 'all' in enforced_os:
452 enforced_os = self.DEPS_OS_CHOICES.itervalues()
453 self._enforced_os = list(set(enforced_os))
454 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000455 self.config_content = None
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000456 # Do not change previous behavior. Only solution level and immediate DEPS
457 # are processed.
458 self._recursion_limit = 2
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000459
460 def SetConfig(self, content):
461 assert self.dependencies == []
462 config_dict = {}
463 self.config_content = content
464 try:
465 exec(content, config_dict)
466 except SyntaxError, e:
467 try:
468 # Try to construct a human readable error message
469 error_message = [
470 'There is a syntax error in your configuration file.',
471 'Line #%s, character %s:' % (e.lineno, e.offset),
472 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
473 except:
474 # Something went wrong, re-raise the original exception
475 raise e
476 else:
477 # Raise a new exception with the human readable message:
478 raise gclient_utils.Error('\n'.join(error_message))
479 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000480 try:
481 self.dependencies.append(Dependency(
482 self, s['name'], s['url'],
483 s.get('safesync_url', None),
484 s.get('custom_deps', {}),
485 s.get('custom_vars', {})))
486 except KeyError:
487 raise gclient_utils.Error('Invalid .gclient file. Solution is '
488 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000489 # .gclient can have hooks.
490 self.deps_hooks = config_dict.get('hooks', [])
491
492 def SaveConfig(self):
493 gclient_utils.FileWrite(os.path.join(self.root_dir(),
494 self._options.config_filename),
495 self.config_content)
496
497 @staticmethod
498 def LoadCurrentConfig(options):
499 """Searches for and loads a .gclient file relative to the current working
500 dir. Returns a GClient object."""
501 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
502 if not path:
503 return None
504 client = GClient(path, options)
505 client.SetConfig(gclient_utils.FileRead(
506 os.path.join(path, options.config_filename)))
507 return client
508
509 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
510 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
511 'solution_name': solution_name,
512 'solution_url': solution_url,
513 'safesync_url' : safesync_url,
514 })
515
516 def _SaveEntries(self, entries):
517 """Creates a .gclient_entries file to record the list of unique checkouts.
518
519 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000520 """
521 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
522 # makes testing a bit too fun.
523 result = pprint.pformat(entries, 2)
524 if result.startswith('{\''):
525 result = '{ \'' + result[2:]
maruel@chromium.org73e21142010-07-05 13:32:01 +0000526 text = 'entries = \\\n' + result + '\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000527 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
528 gclient_utils.FileWrite(file_path, text)
529
530 def _ReadEntries(self):
531 """Read the .gclient_entries file for the given client.
532
533 Returns:
534 A sequence of solution names, which will be empty if there is the
535 entries file hasn't been created yet.
536 """
537 scope = {}
538 filename = os.path.join(self.root_dir(), self._options.entries_filename)
539 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000540 return {}
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000541 exec(gclient_utils.FileRead(filename), scope)
542 return scope['entries']
543
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000544 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000545 """Checks for revision overrides."""
546 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000547 if self._options.head:
548 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000549 for s in self.dependencies:
550 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000551 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000552 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000553 rev = handle.read().strip()
554 handle.close()
555 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000556 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000557 if not self._options.revisions:
558 return revision_overrides
559 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000560 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000561 index = 0
562 for revision in self._options.revisions:
563 if not '@' in revision:
564 # Support for --revision 123
565 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000566 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000567 if not sol in solutions_names:
568 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
569 print >> sys.stderr, ('Please fix your script, having invalid '
570 '--revision flags will soon considered an error.')
571 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000572 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000573 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000574 return revision_overrides
575
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000576 def RunOnDeps(self, command, args):
577 """Runs a command on each dependency in a client and its dependencies.
578
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000579 Args:
580 command: The command to use (e.g., 'status' or 'diff')
581 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000582 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000583 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000584 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000585 revision_overrides = self._EnforceRevisions()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000586
587 # When running runhooks --force, there's no need to consult the SCM.
588 # All known hooks are expected to run unconditionally regardless of working
589 # copy state, so skip the SCM status check.
590 run_scm = not (command == 'runhooks' and self._options.force)
591
592 entries = {}
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000593 file_list = []
594 # Run on the base solutions first.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000595 for solution in self.dependencies:
596 name = solution.name
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000597 if name in entries:
598 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000599 url = solution.url
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000600 entries[name] = url
601 if run_scm and url:
602 self._options.revision = revision_overrides.get(name)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000603 scm = gclient_scm.CreateSCM(url, self.root_dir(), name)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000604 scm.RunCommand(command, self._options, args, file_list)
605 file_list = [os.path.join(name, f.strip()) for f in file_list]
606 self._options.revision = None
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000607
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000608 # Process the dependencies next (sort alphanumerically to ensure that
609 # containing directories get populated first and for readability)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000610 deps = self._ParseAllDeps(entries)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000611 deps_to_process = deps.keys()
612 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000613
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000614 # First pass for direct dependencies.
615 if command == 'update' and not self._options.verbose:
616 pm = Progress('Syncing projects', len(deps_to_process))
617 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000618 if command == 'update' and not self._options.verbose:
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000619 pm.update()
620 if type(deps[d]) == str:
621 url = deps[d]
622 entries[d] = url
623 if run_scm:
624 self._options.revision = revision_overrides.get(d)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000625 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000626 scm.RunCommand(command, self._options, args, file_list)
627 self._options.revision = None
628 elif isinstance(deps[d], self.FileImpl):
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000629 file_dep = deps[d]
630 self._options.revision = file_dep.GetRevision()
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000631 if run_scm:
maruel@chromium.org75a59272010-06-11 22:34:03 +0000632 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000633 scm.RunCommand("updatesingle", self._options,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000634 args + [file_dep.GetFilename()], file_list)
maruel@chromium.org79692d62010-05-14 18:57:13 +0000635
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000636 if command == 'update' and not self._options.verbose:
637 pm.end()
piman@chromium.org6f363722010-04-27 00:41:09 +0000638
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000639 # Second pass for inherited deps (via the From keyword)
640 for d in deps_to_process:
641 if isinstance(deps[d], self.FromImpl):
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000642 # Getting the URL from the sub_deps file can involve having to resolve
643 # a File() or having to resolve a relative URL. To resolve relative
644 # URLs, we need to pass in the orignal sub deps URL.
645 sub_deps_base_url = deps[deps[d].module_name]
maruel@chromium.org271375b2010-06-23 19:17:38 +0000646 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
647 ).ParseDepsFile(False)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000648 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000649 entries[d] = url
650 if run_scm:
651 self._options.revision = revision_overrides.get(d)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000652 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000653 scm.RunCommand(command, self._options, args, file_list)
654 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000655
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000656 # Convert all absolute paths to relative.
657 for i in range(len(file_list)):
658 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
659 # It depends on the command being executed (like runhooks vs sync).
660 if not os.path.isabs(file_list[i]):
661 continue
662
maruel@chromium.org75a59272010-06-11 22:34:03 +0000663 prefix = os.path.commonprefix([self.root_dir().lower(),
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000664 file_list[i].lower()])
665 file_list[i] = file_list[i][len(prefix):]
666
667 # Strip any leading path separators.
668 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
669 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000670
maruel@chromium.org75a59272010-06-11 22:34:03 +0000671 is_using_git = gclient_utils.IsUsingGit(self.root_dir(), entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000672 self._RunHooks(command, file_list, is_using_git)
673
674 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000675 # Notify the user if there is an orphaned entry in their working copy.
676 # Only delete the directory if there are no changes in it, and
677 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000678 prev_entries = self._ReadEntries()
679 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000680 # Fix path separator on Windows.
681 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000682 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000683 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000684 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000685 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000686 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000687 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000688 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000689 else:
690 file_list = []
maruel@chromium.org75a59272010-06-11 22:34:03 +0000691 scm = gclient_scm.CreateSCM(prev_entries[entry], self.root_dir(),
msb@chromium.org83017012009-09-28 18:52:12 +0000692 entry_fixed)
693 scm.status(self._options, [], file_list)
694 modified_files = file_list != []
695 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000696 # There are modified files in this entry. Keep warning until
697 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000698 print(('\nWARNING: \'%s\' is no longer part of this client. '
699 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000700 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000701 else:
702 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000703 print('\n________ deleting \'%s\' in \'%s\'' % (
704 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000705 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000706 # record the current list of entries for next time
707 self._SaveEntries(entries)
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000708 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000709
710 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000711 """Output revision info mapping for the client and its dependencies.
712
713 This allows the capture of an overall "revision" for the source tree that
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000714 can be used to reproduce the same tree in the future. It is only useful for
715 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
716 number or a git hash. A git branch name isn't "pinned" since the actual
717 commit can change.
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000718
719 The --snapshot option allows creating a .gclient file to reproduce the tree.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000720 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000721 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000722 raise gclient_utils.Error('No solution specified')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000723
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000724 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000725 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000726 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000727 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000728 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000729
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000730 # text of the snapshot gclient file
731 new_gclient = ""
732 # Dictionary of { path : SCM url } to ensure no duplicate solutions
733 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000734 entries = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000735 # Run on the base solutions first.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000736 for solution in self.dependencies:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000737 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000738 name = solution.name
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000739 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000740 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000741 (url, rev) = GetURLAndRev(name, solution.url)
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000742 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000743 solution_names[name] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000744
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000745 # Process the dependencies next (sort alphanumerically to ensure that
746 # containing directories get populated first and for readability)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000747 deps = self._ParseAllDeps(entries)
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000748 deps_to_process = deps.keys()
749 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000750
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000751 # First pass for direct dependencies.
752 for d in deps_to_process:
753 if type(deps[d]) == str:
754 (url, rev) = GetURLAndRev(d, deps[d])
755 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000756
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000757 # Second pass for inherited deps (via the From keyword)
758 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000759 if isinstance(deps[d], self.FromImpl):
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000760 deps_parent_url = entries[deps[d].module_name]
761 if deps_parent_url.find("@") < 0:
762 raise gclient_utils.Error("From %s missing revisioned url" %
763 deps[d].module_name)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000764 sub_deps_base_url = deps[deps[d].module_name]
765 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
766 ).ParseDepsFile(False)
767 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
768 (url, rev) = GetURLAndRev(d, url)
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000769 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000770
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000771 # Build the snapshot configuration string
772 if self._options.snapshot:
773 url = entries.pop(name)
maruel@chromium.org73e21142010-07-05 13:32:01 +0000774 custom_deps = ''.join([' \"%s\": \"%s\",\n' % (x, entries[x])
775 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000776
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000777 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000778 'solution_name': name,
779 'solution_url': url,
maruel@chromium.org73e21142010-07-05 13:32:01 +0000780 'safesync_url' : '',
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000781 'solution_deps': custom_deps,
782 }
783 else:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000784 print(';\n'.join(['%s: %s' % (x, entries[x])
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000785 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000786
787 # Print the snapshot configuration file
788 if self._options.snapshot:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000789 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
maruel@chromium.org75a59272010-06-11 22:34:03 +0000790 snapclient = GClient(self.root_dir(), self._options)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000791 snapclient.SetConfig(config)
maruel@chromium.org116704f2010-06-11 17:34:38 +0000792 print(snapclient.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000793
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000794 def ParseDepsFile(self, direct_reference):
795 """No DEPS to parse for a .gclient file."""
796 self.direct_reference = direct_reference
797 self.deps_parsed = True
798
maruel@chromium.org75a59272010-06-11 22:34:03 +0000799 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000800 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000801 return self._root_dir
802
maruel@chromium.org271375b2010-06-23 19:17:38 +0000803 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000804 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000805 return self._enforced_os
806
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000807 def recursion_limit(self):
808 """How recursive can each dependencies in DEPS file can load DEPS file."""
809 return self._recursion_limit
810
811 def tree(self, force_all):
812 """Returns a flat list of all the dependencies."""
813 def subtree(dep):
814 if not force_all and not dep.direct_reference:
815 # Was loaded from a From() keyword in a DEPS file, don't load all its
816 # dependencies.
817 return []
818 result = dep.dependencies[:]
819 for d in dep.dependencies:
820 result.extend(subtree(d))
821 return result
822 return subtree(self)
823
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000824
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000825#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000826
827
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000828def CMDcleanup(parser, args):
829 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000830
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000831Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000832"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000833 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
834 help='override deps for the specified (comma-separated) '
835 'platform(s); \'all\' will process all deps_os '
836 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000837 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000838 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000839 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000840 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000841 if options.verbose:
842 # Print out the .gclient file. This is longer than if we just printed the
843 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000844 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000845 return client.RunOnDeps('cleanup', args)
846
847
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000848@attr('usage', '[command] [args ...]')
849def CMDrecurse(parser, args):
850 """Operates on all the entries.
851
852 Runs a shell command on all entries.
853 """
854 # Stop parsing at the first non-arg so that these go through to the command
855 parser.disable_interspersed_args()
856 parser.add_option('-s', '--scm', action='append', default=[],
857 help='choose scm types to operate upon')
858 options, args = parser.parse_args(args)
859 root, entries = gclient_utils.GetGClientRootAndEntries()
860 scm_set = set()
861 for scm in options.scm:
862 scm_set.update(scm.split(','))
863
864 # Pass in the SCM type as an env variable
865 env = os.environ.copy()
866
867 for path, url in entries.iteritems():
868 scm = gclient_scm.GetScmName(url)
869 if scm_set and scm not in scm_set:
870 continue
871 dir = os.path.normpath(os.path.join(root, path))
872 env['GCLIENT_SCM'] = scm
873 env['GCLIENT_URL'] = url
874 subprocess.Popen(args, cwd=dir, env=env).communicate()
875
876
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000877@attr('usage', '[url] [safesync url]')
878def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000879 """Create a .gclient file in the current directory.
880
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000881This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000882top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000883modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000884provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000885URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000886"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000887 parser.add_option('--spec',
888 help='create a gclient file containing the provided '
889 'string. Due to Cygwin/Python brokenness, it '
890 'probably can\'t contain any newlines.')
891 parser.add_option('--name',
892 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000893 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000894 if ((options.spec and args) or len(args) > 2 or
895 (not options.spec and not args)):
896 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
897
maruel@chromium.org0329e672009-05-13 18:41:04 +0000898 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000899 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000900 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000901 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000902 if options.spec:
903 client.SetConfig(options.spec)
904 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000905 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000906 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000907 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000908 else:
909 # specify an alternate relpath for the given URL.
910 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000911 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000912 if len(args) > 1:
913 safesync_url = args[1]
914 client.SetDefaultConfig(name, base_url, safesync_url)
915 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000916 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000917
918
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000919def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000920 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000921 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
922 help='override deps for the specified (comma-separated) '
923 'platform(s); \'all\' will process all deps_os '
924 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000925 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000926 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000927 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000928 client = GClient.LoadCurrentConfig(options)
929
930 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000931 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000932
933 if options.verbose:
934 # Print out the .gclient file. This is longer than if we just printed the
935 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000936 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000937 return client.RunOnDeps('export', args)
938
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000939
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000940@attr('epilog', """Example:
941 gclient pack > patch.txt
942 generate simple patch for configured client and dependences
943""")
944def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000945 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000946
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000947Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000948dependencies, and performs minimal postprocessing of the output. The
949resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000950checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000951"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000952 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
953 help='override deps for the specified (comma-separated) '
954 'platform(s); \'all\' will process all deps_os '
955 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000956 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000957 client = GClient.LoadCurrentConfig(options)
958 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000959 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000960 if options.verbose:
961 # Print out the .gclient file. This is longer than if we just printed the
962 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000963 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000964 return client.RunOnDeps('pack', args)
965
966
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000967def CMDstatus(parser, args):
968 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000969 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
970 help='override deps for the specified (comma-separated) '
971 'platform(s); \'all\' will process all deps_os '
972 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000973 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000974 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000975 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000976 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000977 if options.verbose:
978 # Print out the .gclient file. This is longer than if we just printed the
979 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000980 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000981 return client.RunOnDeps('status', args)
982
983
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000984@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000985 gclient sync
986 update files from SCM according to current configuration,
987 *for modules which have changed since last update or sync*
988 gclient sync --force
989 update files from SCM according to current configuration, for
990 all modules (useful for recovering files deleted from local copy)
991 gclient sync --revision src@31000
992 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000993""")
994def CMDsync(parser, args):
995 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000996 parser.add_option('-f', '--force', action='store_true',
997 help='force update even for unchanged modules')
998 parser.add_option('-n', '--nohooks', action='store_true',
999 help='don\'t run hooks after the update is complete')
1000 parser.add_option('-r', '--revision', action='append',
1001 dest='revisions', metavar='REV', default=[],
1002 help='Enforces revision/hash for the solutions with the '
1003 'format src@rev. The src@ part is optional and can be '
1004 'skipped. -r can be used multiple times when .gclient '
1005 'has multiple solutions configured and will work even '
1006 'if the src@ part is skipped.')
1007 parser.add_option('-H', '--head', action='store_true',
1008 help='skips any safesync_urls specified in '
1009 'configured solutions and sync to head instead')
1010 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1011 help='delete any unexpected unversioned trees '
1012 'that are in the checkout')
1013 parser.add_option('-R', '--reset', action='store_true',
1014 help='resets any local changes before updating (git only)')
1015 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1016 help='override deps for the specified (comma-separated) '
1017 'platform(s); \'all\' will process all deps_os '
1018 'references')
1019 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1020 help='Skip svn up whenever possible by requesting '
1021 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001022 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001023 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001024
1025 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001026 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001027
maruel@chromium.org307d1792010-05-31 20:03:13 +00001028 if options.revisions and options.head:
1029 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001030 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001031
1032 if options.verbose:
1033 # Print out the .gclient file. This is longer than if we just printed the
1034 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001035 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001036 return client.RunOnDeps('update', args)
1037
1038
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001039def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001040 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001041 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001043def CMDdiff(parser, args):
1044 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001045 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1046 help='override deps for the specified (comma-separated) '
1047 'platform(s); \'all\' will process all deps_os '
1048 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001049 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001050 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001051 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001052 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053 if options.verbose:
1054 # Print out the .gclient file. This is longer than if we just printed the
1055 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001056 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001057 return client.RunOnDeps('diff', args)
1058
1059
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001060def CMDrevert(parser, args):
1061 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001062 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1063 help='override deps for the specified (comma-separated) '
1064 'platform(s); \'all\' will process all deps_os '
1065 'references')
1066 parser.add_option('-n', '--nohooks', action='store_true',
1067 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001068 (options, args) = parser.parse_args(args)
1069 # --force is implied.
1070 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001071 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001072 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001073 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001074 return client.RunOnDeps('revert', args)
1075
1076
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001077def CMDrunhooks(parser, args):
1078 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001079 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1080 help='override deps for the specified (comma-separated) '
1081 'platform(s); \'all\' will process all deps_os '
1082 'references')
1083 parser.add_option('-f', '--force', action='store_true', default=True,
1084 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001085 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001086 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001087 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001088 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001089 if options.verbose:
1090 # Print out the .gclient file. This is longer than if we just printed the
1091 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001092 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001093 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001094 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001095 return client.RunOnDeps('runhooks', args)
1096
1097
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001098def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001099 """Output revision info mapping for the client and its dependencies.
1100
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001101 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001102 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001103 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1104 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001105 commit can change.
1106 """
1107 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1108 help='override deps for the specified (comma-separated) '
1109 'platform(s); \'all\' will process all deps_os '
1110 'references')
1111 parser.add_option('-s', '--snapshot', action='store_true',
1112 help='creates a snapshot .gclient file of the current '
1113 'version of all repositories to reproduce the tree, '
1114 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001115 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001116 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001117 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001118 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001119 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001120 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001121
1122
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001123def Command(name):
1124 return getattr(sys.modules[__name__], 'CMD' + name, None)
1125
1126
1127def CMDhelp(parser, args):
1128 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001129 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001130 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001131 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001132 parser.print_help()
1133 return 0
1134
1135
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001136def GenUsage(parser, command):
1137 """Modify an OptParse object with the function's documentation."""
1138 obj = Command(command)
1139 if command == 'help':
1140 command = '<command>'
1141 # OptParser.description prefer nicely non-formatted strings.
1142 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1143 usage = getattr(obj, 'usage', '')
1144 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1145 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001146
1147
1148def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001149 """Doesn't parse the arguments here, just find the right subcommand to
1150 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001151 try:
1152 # Do it late so all commands are listed.
1153 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1154 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1155 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1156 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001157 parser.add_option('-v', '--verbose', action='count', default=0,
1158 help='Produces additional output for diagnostics. Can be '
1159 'used up to three times for more logging info.')
1160 parser.add_option('--gclientfile', dest='config_filename',
1161 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1162 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001163 # Integrate standard options processing.
1164 old_parser = parser.parse_args
1165 def Parse(args):
1166 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001167 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001168 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001169 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001170 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001171 level = logging.DEBUG
1172 logging.basicConfig(level=level,
1173 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1174 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001175 if not hasattr(options, 'revisions'):
1176 # GClient.RunOnDeps expects it even if not applicable.
1177 options.revisions = []
1178 if not hasattr(options, 'head'):
1179 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001180 if not hasattr(options, 'nohooks'):
1181 options.nohooks = True
1182 if not hasattr(options, 'deps_os'):
1183 options.deps_os = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001184 return (options, args)
1185 parser.parse_args = Parse
1186 # We don't want wordwrapping in epilog (usually examples)
1187 parser.format_epilog = lambda _: parser.epilog or ''
1188 if argv:
1189 command = Command(argv[0])
1190 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001191 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001192 GenUsage(parser, argv[0])
1193 return command(parser, argv[1:])
1194 # Not a known command. Default to help.
1195 GenUsage(parser, 'help')
1196 return CMDhelp(parser, argv)
1197 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001198 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001199 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001200
1201
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001202if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001203 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001204
1205# vim: ts=2:sw=2:tw=80:et: