blob: 9721c55ff50db194276929c98a02c467dee42916 [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
6"""A wrapper script to manage a set of client modules in different SCM.
7
8This script is intended to be used to help basic management of client
msb@chromium.orgd6504212010-01-13 17:34:31 +00009program sources residing in one or more Subversion modules and Git
10repositories, along with other modules it depends on, also in Subversion or Git,
11but possibly on multiple respositories, making a wrapper system apparently
12necessary.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000013
14Files
15 .gclient : Current client configuration, written by 'config' command.
16 Format is a Python script defining 'solutions', a list whose
17 entries each are maps binding the strings "name" and "url"
18 to strings specifying the name and location of the client
19 module, as well as "custom_deps" to a map similar to the DEPS
20 file below.
21 .gclient_entries : A cache constructed by 'update' command. Format is a
22 Python script defining 'entries', a list of the names
23 of all modules in the client
24 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
25 submodule name to a URL where it can be found (via one SCM)
26
27Hooks
28 .gclient and DEPS files may optionally contain a list named "hooks" to
29 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000030 working copy as a result of a "sync"/"update" or "revert" operation. This
31 could be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000032 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000033 --force, all known hooks will run regardless of the state of the working
34 copy.
35
36 Each item in a "hooks" list is a dict, containing these two keys:
37 "pattern" The associated value is a string containing a regular
38 expression. When a file whose pathname matches the expression
39 is checked out, updated, or reverted, the hook's "action" will
40 run.
41 "action" A list describing a command to run along with its arguments, if
42 any. An action command will run at most one time per gclient
43 invocation, regardless of how many files matched the pattern.
44 The action is executed in the same directory as the .gclient
45 file. If the first item in the list is the string "python",
46 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000047 to run the command. If the list contains string "$matching_files"
48 it will be removed from the list and the list will be extended
49 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000050
51 Example:
52 hooks = [
53 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
54 "action": ["python", "image_indexer.py", "--all"]},
55 ]
56"""
57
maruel@chromium.org5ca27692010-05-26 19:32:41 +000058__version__ = "0.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000059
60import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000061import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import optparse
63import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000064import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000065import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000066import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000067import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000068import urllib
69
maruel@chromium.orgada4c652009-12-03 15:32:01 +000070import breakpad
71
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000072import gclient_scm
73import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000074from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000075
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000077def attr(attr, data):
78 """Sets an attribute on a function."""
79 def hook(fn):
80 setattr(fn, attr, data)
81 return fn
82 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000083
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085## GClient implementation.
86
87
maruel@chromium.org116704f2010-06-11 17:34:38 +000088class GClientKeywords(object):
89 class FromImpl(object):
90 """Used to implement the From() syntax."""
91
92 def __init__(self, module_name, sub_target_name=None):
93 """module_name is the dep module we want to include from. It can also be
94 the name of a subdirectory to include from.
95
96 sub_target_name is an optional parameter if the module name in the other
97 DEPS file is different. E.g., you might want to map src/net to net."""
98 self.module_name = module_name
99 self.sub_target_name = sub_target_name
100
101 def __str__(self):
102 return 'From(%s, %s)' % (repr(self.module_name),
103 repr(self.sub_target_name))
104
105 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
106 """Resolve the URL for this From entry."""
107 sub_deps_target_name = target_name
108 if self.sub_target_name:
109 sub_deps_target_name = self.sub_target_name
110 url = sub_deps[sub_deps_target_name]
111 if url.startswith('/'):
112 # If it's a relative URL, we need to resolve the URL relative to the
113 # sub deps base URL.
114 if not isinstance(sub_deps_base_url, basestring):
115 sub_deps_base_url = sub_deps_base_url.GetPath()
116 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
117 None)
118 url = scm.FullUrlForRelativeUrl(url)
119 return url
120
121 class FileImpl(object):
122 """Used to implement the File('') syntax which lets you sync a single file
123 from an SVN repo."""
124
125 def __init__(self, file_location):
126 self.file_location = file_location
127
128 def __str__(self):
129 return 'File("%s")' % self.file_location
130
131 def GetPath(self):
132 return os.path.split(self.file_location)[0]
133
134 def GetFilename(self):
135 rev_tokens = self.file_location.split('@')
136 return os.path.split(rev_tokens[0])[1]
137
138 def GetRevision(self):
139 rev_tokens = self.file_location.split('@')
140 if len(rev_tokens) > 1:
141 return rev_tokens[1]
142 return None
143
144 class VarImpl(object):
145 def __init__(self, custom_vars, local_scope):
146 self._custom_vars = custom_vars
147 self._local_scope = local_scope
148
149 def Lookup(self, var_name):
150 """Implements the Var syntax."""
151 if var_name in self._custom_vars:
152 return self._custom_vars[var_name]
153 elif var_name in self._local_scope.get("vars", {}):
154 return self._local_scope["vars"][var_name]
155 raise gclient_utils.Error("Var is not defined: %s" % var_name)
156
157
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000158class Dependency(GClientKeywords):
159 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000160 DEPS_FILE = 'DEPS'
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000161 def __init__(self, parent, name, url, safesync_url=None, custom_deps=None,
162 custom_vars=None, deps_file=None):
163 GClientKeywords.__init__(self)
164 self.parent = parent
165 self.name = name
166 self.url = url
167 # These 2 are only set in .gclient and not in DEPS files.
168 self.safesync_url = safesync_url
169 self.custom_vars = custom_vars or {}
170 self.custom_deps = custom_deps or {}
171 self.dependencies = []
172 self.deps_file = deps_file or self.DEPS_FILE
173 self._deps_hooks = []
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000174
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000175 # Sanity checks
176 if not self.name and self.parent:
177 raise gclient_utils.Error('Dependency without name')
178 if not isinstance(self.url,
179 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
180 raise gclient_utils.Error('dependency url must be either a string, None, '
181 'File() or From() instead of %s' %
182 self.url.__class__.__name__)
183 if '/' in self.deps_file or '\\' in self.deps_file:
184 raise gclient_utils.Error('deps_file name must not be a path, just a '
185 'filename. %s' % self.deps_file)
186
187
188class GClient(Dependency):
189 """Main gclient checkout root where .gclient resides."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000190 SUPPORTED_COMMANDS = [
kbr@google.comab318592009-09-04 00:54:55 +0000191 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
192 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000193 ]
194
maruel@chromium.org116704f2010-06-11 17:34:38 +0000195 DEPS_OS_CHOICES = {
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000196 "win32": "win",
197 "win": "win",
198 "cygwin": "win",
199 "darwin": "mac",
200 "mac": "mac",
201 "unix": "unix",
202 "linux": "unix",
203 "linux2": "unix",
204 }
205
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000206 DEFAULT_CLIENT_FILE_TEXT = ("""\
207solutions = [
208 { "name" : "%(solution_name)s",
209 "url" : "%(solution_url)s",
210 "custom_deps" : {
211 },
212 "safesync_url": "%(safesync_url)s"
213 },
214]
215""")
216
217 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
218 { "name" : "%(solution_name)s",
219 "url" : "%(solution_url)s",
220 "custom_deps" : {
221 %(solution_deps)s,
222 },
223 "safesync_url": "%(safesync_url)s"
224 },
225""")
226
227 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
228# Snapshot generated with gclient revinfo --snapshot
229solutions = [
230%(solution_list)s
231]
232""")
233
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000234 def __init__(self, root_dir, options):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000235 Dependency.__init__(self, None, None, None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000236 self._root_dir = root_dir
237 self._options = options
maruel@chromium.org116704f2010-06-11 17:34:38 +0000238 self.config_content = None
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000239
240 def SetConfig(self, content):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000241 config_dict = {}
maruel@chromium.org116704f2010-06-11 17:34:38 +0000242 self.config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000243 try:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000244 exec(content, config_dict)
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000245 except SyntaxError, e:
246 try:
247 # Try to construct a human readable error message
248 error_message = [
249 'There is a syntax error in your configuration file.',
250 'Line #%s, character %s:' % (e.lineno, e.offset),
251 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
252 except:
253 # Something went wrong, re-raise the original exception
254 raise e
255 else:
256 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000257 raise gclient_utils.Error('\n'.join(error_message))
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000258 for s in config_dict.get('solutions', []):
259 self.dependencies.append(Dependency(
260 self, s['name'], s['url'],
261 s.get('safesync_url', None),
262 s.get('custom_deps', {}),
263 s.get('custom_vars', {})))
264 # .gclient can have hooks.
265 self._deps_hooks = config_dict.get('hooks', [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000266
267 def SaveConfig(self):
maruel@chromium.org75a59272010-06-11 22:34:03 +0000268 gclient_utils.FileWrite(os.path.join(self.root_dir(),
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000269 self._options.config_filename),
maruel@chromium.org116704f2010-06-11 17:34:38 +0000270 self.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000271
272 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000273 client_source = gclient_utils.FileRead(
maruel@chromium.org75a59272010-06-11 22:34:03 +0000274 os.path.join(self.root_dir(), self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000275 self.SetConfig(client_source)
276
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000277 @staticmethod
278 def LoadCurrentConfig(options, from_dir=None):
279 """Searches for and loads a .gclient file relative to the current working
280 dir.
281
282 Returns:
283 A dict representing the contents of the .gclient file or an empty dict if
284 the .gclient file doesn't exist.
285 """
286 if not from_dir:
287 from_dir = os.curdir
288 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000289 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000290 split_path = os.path.split(path)
291 if not split_path[1]:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000292 return None
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000293 path = split_path[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000294 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000295 client._LoadConfig()
296 return client
297
298 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000299 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000300 'solution_name': solution_name,
301 'solution_url': solution_url,
302 'safesync_url' : safesync_url,
303 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000304
305 def _SaveEntries(self, entries):
306 """Creates a .gclient_entries file to record the list of unique checkouts.
307
308 The .gclient_entries file lives in the same directory as .gclient.
309
310 Args:
311 entries: A sequence of solution names.
312 """
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000313 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
314 # makes testing a bit too fun.
315 result = pprint.pformat(entries, 2)
316 if result.startswith('{\''):
maruel@chromium.org1edec4d2010-04-08 17:17:06 +0000317 result = '{ \'' + result[2:]
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000318 text = "entries = \\\n" + result + '\n'
maruel@chromium.org75a59272010-06-11 22:34:03 +0000319 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000320 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000321
322 def _ReadEntries(self):
323 """Read the .gclient_entries file for the given client.
324
325 Args:
326 client: The client for which the entries file should be read.
327
328 Returns:
329 A sequence of solution names, which will be empty if there is the
330 entries file hasn't been created yet.
331 """
332 scope = {}
maruel@chromium.org75a59272010-06-11 22:34:03 +0000333 filename = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000334 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000335 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000336 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000337 return scope["entries"]
338
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000339 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000340 custom_vars, parse_hooks):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000341 """Parses the DEPS file for the specified solution.
342
343 Args:
344 solution_name: The name of the solution to query.
345 solution_deps_content: Content of the DEPS file for the solution
346 custom_vars: A dict of vars to override any vars defined in the DEPS file.
347
348 Returns:
349 A dict mapping module names (as relative paths) to URLs or an empty
350 dict if the solution does not have a DEPS file.
351 """
352 # Skip empty
353 if not solution_deps_content:
354 return {}
355 # Eval the content
356 local_scope = {}
maruel@chromium.org116704f2010-06-11 17:34:38 +0000357 var = self.VarImpl(custom_vars, local_scope)
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000358 global_scope = {
359 "File": self.FileImpl,
360 "From": self.FromImpl,
361 "Var": var.Lookup,
362 "deps_os": {},
363 }
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000364 exec(solution_deps_content, global_scope, local_scope)
365 deps = local_scope.get("deps", {})
366
367 # load os specific dependencies if defined. these dependencies may
368 # override or extend the values defined by the 'deps' member.
369 if "deps_os" in local_scope:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000370 if self._options.deps_os is not None:
371 deps_to_include = self._options.deps_os.split(",")
372 if "all" in deps_to_include:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000373 deps_to_include = list(set(self.DEPS_OS_CHOICES.itervalues()))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000374 else:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000375 deps_to_include = [self.DEPS_OS_CHOICES.get(sys.platform, "unix")]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000376
377 deps_to_include = set(deps_to_include)
378 for deps_os_key in deps_to_include:
379 os_deps = local_scope["deps_os"].get(deps_os_key, {})
380 if len(deps_to_include) > 1:
381 # Ignore any overrides when including deps for more than one
382 # platform, so we collect the broadest set of dependencies available.
383 # We may end up with the wrong revision of something for our
384 # platform, but this is the best we can do.
385 deps.update([x for x in os_deps.items() if not x[0] in deps])
386 else:
387 deps.update(os_deps)
388
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000389 if 'hooks' in local_scope and parse_hooks:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000390 # TODO(maruel): Temporary Hack. Since this function is misplaced, find the
391 # right 'self' to add the hooks.
392 for d in self.dependencies:
393 if d.name == solution_name:
394 d._deps_hooks.extend(local_scope['hooks'])
395 break
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000396
397 # If use_relative_paths is set in the DEPS file, regenerate
398 # the dictionary using paths relative to the directory containing
399 # the DEPS file.
400 if local_scope.get('use_relative_paths'):
401 rel_deps = {}
402 for d, url in deps.items():
403 # normpath is required to allow DEPS to use .. in their
404 # dependency local path.
405 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
406 return rel_deps
407 else:
408 return deps
409
410 def _ParseAllDeps(self, solution_urls, solution_deps_content):
411 """Parse the complete list of dependencies for the client.
412
413 Args:
414 solution_urls: A dict mapping module names (as relative paths) to URLs
415 corresponding to the solutions specified by the client. This parameter
416 is passed as an optimization.
417 solution_deps_content: A dict mapping module names to the content
418 of their DEPS files
419
420 Returns:
421 A dict mapping module names (as relative paths) to URLs corresponding
422 to the entire set of dependencies to checkout for the given client.
423
424 Raises:
425 Error: If a dependency conflicts with another dependency or of a solution.
426 """
427 deps = {}
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000428 for solution in self.dependencies:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000429 solution_deps = self._ParseSolutionDeps(
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000430 solution.name,
431 solution_deps_content[solution.name],
432 solution.custom_vars,
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000433 True)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000434
435 # If a line is in custom_deps, but not in the solution, we want to append
436 # this line to the solution.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000437 for d in solution.custom_deps:
438 if d not in solution_deps:
439 solution_deps[d] = solution.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000440
441 for d in solution_deps:
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000442 if d in solution.custom_deps:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000443 # Dependency is overriden.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000444 url = solution.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000445 if url is None:
446 continue
447 else:
448 url = solution_deps[d]
449 # if we have a From reference dependent on another solution, then
450 # just skip the From reference. When we pull deps for the solution,
451 # we will take care of this dependency.
452 #
453 # If multiple solutions all have the same From reference, then we
454 # should only add one to our list of dependencies.
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000455 if isinstance(url, self.FromImpl):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000456 if url.module_name in solution_urls:
457 # Already parsed.
458 continue
459 if d in deps and type(deps[d]) != str:
460 if url.module_name == deps[d].module_name:
461 continue
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000462 elif isinstance(url, str):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000463 parsed_url = urlparse.urlparse(url)
464 scheme = parsed_url[0]
465 if not scheme:
466 # A relative url. Fetch the real base.
467 path = parsed_url[2]
468 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000469 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000470 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000471 # Create a scm just to query the full url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000472 scm = gclient_scm.CreateSCM(solution.url, self.root_dir(),
473 None)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000474 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000475 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000476 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000477 "Solutions have conflicting versions of dependency \"%s\"" % d)
478 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000479 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000480 "Dependency \"%s\" conflicts with specified solution" % d)
481 # Grab the dependency.
482 deps[d] = url
483 return deps
484
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000485 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000486 """Runs the action from a single hook.
487 """
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000488 logging.info(hook_dict)
489 logging.info(matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000490 command = hook_dict['action'][:]
491 if command[0] == 'python':
492 # If the hook specified "python" as the first item, the action is a
493 # Python script. Run it by starting a new copy of the same
494 # interpreter.
495 command[0] = sys.executable
496
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000497 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000498 splice_index = command.index('$matching_files')
499 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000500
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000501 # Use a discrete exit status code of 2 to indicate that a hook action
502 # failed. Users of this script may wish to treat hook action failures
503 # differently from VC failures.
maruel@chromium.org75a59272010-06-11 22:34:03 +0000504 gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000505
506 def _RunHooks(self, command, file_list, is_using_git):
507 """Evaluates all hooks, running actions as needed.
508 """
509 # Hooks only run for these command types.
510 if not command in ('update', 'revert', 'runhooks'):
511 return
512
evan@chromium.org67820ef2009-07-27 17:23:00 +0000513 # Hooks only run when --nohooks is not specified
514 if self._options.nohooks:
515 return
516
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000517 # Get any hooks from the .gclient file.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000518 hooks = self._deps_hooks[:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000519 # Add any hooks found in DEPS files.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000520 for d in self.dependencies:
521 hooks.extend(d._deps_hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000522
523 # If "--force" was specified, run all hooks regardless of what files have
524 # changed. If the user is using git, then we don't know what files have
525 # changed so we always run all hooks.
526 if self._options.force or is_using_git:
527 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000528 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000529 return
530
531 # Run hooks on the basis of whether the files from the gclient operation
532 # match each hook's pattern.
533 for hook_dict in hooks:
534 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000535 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000536 if matching_file_list:
537 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000538
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000539 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000540 """Checks for revision overrides."""
541 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000542 if self._options.head:
543 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000544 for s in self.dependencies:
545 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000546 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000547 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000548 rev = handle.read().strip()
549 handle.close()
550 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000551 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000552 if not self._options.revisions:
553 return revision_overrides
554 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000555 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000556 index = 0
557 for revision in self._options.revisions:
558 if not '@' in revision:
559 # Support for --revision 123
560 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000561 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000562 if not sol in solutions_names:
563 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
564 print >> sys.stderr, ('Please fix your script, having invalid '
565 '--revision flags will soon considered an error.')
566 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000567 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000568 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000569 return revision_overrides
570
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000571 def RunOnDeps(self, command, args):
572 """Runs a command on each dependency in a client and its dependencies.
573
574 The module's dependencies are specified in its top-level DEPS files.
575
576 Args:
577 command: The command to use (e.g., 'status' or 'diff')
578 args: list of str - extra arguments to add to the command line.
579
580 Raises:
581 Error: If the client has conflicting entries.
582 """
maruel@chromium.org116704f2010-06-11 17:34:38 +0000583 if not command in self.SUPPORTED_COMMANDS:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000584 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000585
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000586 if not self.dependencies:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000587 raise gclient_utils.Error("No solution specified")
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000588 revision_overrides = self._EnforceRevisions()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000589
590 # When running runhooks --force, there's no need to consult the SCM.
591 # All known hooks are expected to run unconditionally regardless of working
592 # copy state, so skip the SCM status check.
593 run_scm = not (command == 'runhooks' and self._options.force)
594
595 entries = {}
596 entries_deps_content = {}
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000597 file_list = []
598 # Run on the base solutions first.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000599 for solution in self.dependencies:
600 name = solution.name
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000601 if name in entries:
602 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000603 url = solution.url
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000604 entries[name] = url
605 if run_scm and url:
606 self._options.revision = revision_overrides.get(name)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000607 scm = gclient_scm.CreateSCM(url, self.root_dir(), name)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000608 scm.RunCommand(command, self._options, args, file_list)
609 file_list = [os.path.join(name, f.strip()) for f in file_list]
610 self._options.revision = None
611 try:
612 deps_content = gclient_utils.FileRead(
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000613 os.path.join(self.root_dir(), name, solution.deps_file))
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000614 except IOError, e:
615 if e.errno != errno.ENOENT:
616 raise
617 deps_content = ""
618 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000619
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000620 # Process the dependencies next (sort alphanumerically to ensure that
621 # containing directories get populated first and for readability)
622 deps = self._ParseAllDeps(entries, entries_deps_content)
623 deps_to_process = deps.keys()
624 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000625
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000626 # First pass for direct dependencies.
627 if command == 'update' and not self._options.verbose:
628 pm = Progress('Syncing projects', len(deps_to_process))
629 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000630 if command == 'update' and not self._options.verbose:
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000631 pm.update()
632 if type(deps[d]) == str:
633 url = deps[d]
634 entries[d] = url
635 if run_scm:
636 self._options.revision = revision_overrides.get(d)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000637 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000638 scm.RunCommand(command, self._options, args, file_list)
639 self._options.revision = None
640 elif isinstance(deps[d], self.FileImpl):
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000641 file_dep = deps[d]
642 self._options.revision = file_dep.GetRevision()
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000643 if run_scm:
maruel@chromium.org75a59272010-06-11 22:34:03 +0000644 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000645 scm.RunCommand("updatesingle", self._options,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000646 args + [file_dep.GetFilename()], file_list)
maruel@chromium.org79692d62010-05-14 18:57:13 +0000647
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000648 if command == 'update' and not self._options.verbose:
649 pm.end()
piman@chromium.org6f363722010-04-27 00:41:09 +0000650
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000651 # Second pass for inherited deps (via the From keyword)
652 for d in deps_to_process:
653 if isinstance(deps[d], self.FromImpl):
maruel@chromium.org75a59272010-06-11 22:34:03 +0000654 filename = os.path.join(self.root_dir(),
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000655 deps[d].module_name,
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000656 self.DEPS_FILE)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000657 content = gclient_utils.FileRead(filename)
658 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
659 False)
660 # Getting the URL from the sub_deps file can involve having to resolve
661 # a File() or having to resolve a relative URL. To resolve relative
662 # URLs, we need to pass in the orignal sub deps URL.
663 sub_deps_base_url = deps[deps[d].module_name]
maruel@chromium.org75a59272010-06-11 22:34:03 +0000664 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000665 entries[d] = url
666 if run_scm:
667 self._options.revision = revision_overrides.get(d)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000668 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000669 scm.RunCommand(command, self._options, args, file_list)
670 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000671
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000672 # Convert all absolute paths to relative.
673 for i in range(len(file_list)):
674 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
675 # It depends on the command being executed (like runhooks vs sync).
676 if not os.path.isabs(file_list[i]):
677 continue
678
maruel@chromium.org75a59272010-06-11 22:34:03 +0000679 prefix = os.path.commonprefix([self.root_dir().lower(),
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000680 file_list[i].lower()])
681 file_list[i] = file_list[i][len(prefix):]
682
683 # Strip any leading path separators.
684 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
685 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000686
maruel@chromium.org75a59272010-06-11 22:34:03 +0000687 is_using_git = gclient_utils.IsUsingGit(self.root_dir(), entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000688 self._RunHooks(command, file_list, is_using_git)
689
690 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000691 # Notify the user if there is an orphaned entry in their working copy.
692 # Only delete the directory if there are no changes in it, and
693 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000694 prev_entries = self._ReadEntries()
695 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000696 # Fix path separator on Windows.
697 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000698 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000699 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000700 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000701 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000702 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000703 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000704 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000705 else:
706 file_list = []
maruel@chromium.org75a59272010-06-11 22:34:03 +0000707 scm = gclient_scm.CreateSCM(prev_entries[entry], self.root_dir(),
msb@chromium.org83017012009-09-28 18:52:12 +0000708 entry_fixed)
709 scm.status(self._options, [], file_list)
710 modified_files = file_list != []
711 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000712 # There are modified files in this entry. Keep warning until
713 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000714 print(("\nWARNING: \"%s\" is no longer part of this client. "
715 "It is recommended that you manually remove it.\n") %
716 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000717 else:
718 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000719 print("\n________ deleting \'%s\' " +
maruel@chromium.org75a59272010-06-11 22:34:03 +0000720 "in \'%s\'") % (entry_fixed, self.root_dir())
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000721 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000722 # record the current list of entries for next time
723 self._SaveEntries(entries)
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000724 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000725
726 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000727 """Output revision info mapping for the client and its dependencies.
728
729 This allows the capture of an overall "revision" for the source tree that
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000730 can be used to reproduce the same tree in the future. It is only useful for
731 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
732 number or a git hash. A git branch name isn't "pinned" since the actual
733 commit can change.
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000734
735 The --snapshot option allows creating a .gclient file to reproduce the tree.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000736 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000737 if not self.dependencies:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000738 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000739
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000740 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000741 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000742 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000743 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000744 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000745
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000746 # text of the snapshot gclient file
747 new_gclient = ""
748 # Dictionary of { path : SCM url } to ensure no duplicate solutions
749 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000750 entries = {}
751 entries_deps_content = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000752 # Run on the base solutions first.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000753 for solution in self.dependencies:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000754 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000755 name = solution.name
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000756 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000757 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000758 (url, rev) = GetURLAndRev(name, solution.url)
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000759 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000760 solution_names[name] = "%s@%s" % (url, rev)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000761 deps_file = solution.deps_file
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000762 if '/' in deps_file or '\\' in deps_file:
763 raise gclient_utils.Error('deps_file name must not be a path, just a '
764 'filename.')
765 try:
766 deps_content = gclient_utils.FileRead(
maruel@chromium.org75a59272010-06-11 22:34:03 +0000767 os.path.join(self.root_dir(), name, deps_file))
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000768 except IOError, e:
769 if e.errno != errno.ENOENT:
770 raise
771 deps_content = ""
772 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000773
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000774 # Process the dependencies next (sort alphanumerically to ensure that
775 # containing directories get populated first and for readability)
776 deps = self._ParseAllDeps(entries, entries_deps_content)
777 deps_to_process = deps.keys()
778 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000779
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000780 # First pass for direct dependencies.
781 for d in deps_to_process:
782 if type(deps[d]) == str:
783 (url, rev) = GetURLAndRev(d, deps[d])
784 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000785
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000786 # Second pass for inherited deps (via the From keyword)
787 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000788 if isinstance(deps[d], self.FromImpl):
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000789 deps_parent_url = entries[deps[d].module_name]
790 if deps_parent_url.find("@") < 0:
791 raise gclient_utils.Error("From %s missing revisioned url" %
792 deps[d].module_name)
793 content = gclient_utils.FileRead(os.path.join(
maruel@chromium.org75a59272010-06-11 22:34:03 +0000794 self.root_dir(),
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000795 deps[d].module_name,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000796 self.DEPS_FILE))
797 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
798 False)
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000799 (url, rev) = GetURLAndRev(d, sub_deps[d])
800 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000801
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000802 # Build the snapshot configuration string
803 if self._options.snapshot:
804 url = entries.pop(name)
805 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
806 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000807
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000808 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000809 'solution_name': name,
810 'solution_url': url,
811 'safesync_url' : "",
812 'solution_deps': custom_deps,
813 }
814 else:
815 print(";\n".join(["%s: %s" % (x, entries[x])
816 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000817
818 # Print the snapshot configuration file
819 if self._options.snapshot:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000820 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
maruel@chromium.org75a59272010-06-11 22:34:03 +0000821 snapclient = GClient(self.root_dir(), self._options)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000822 snapclient.SetConfig(config)
maruel@chromium.org116704f2010-06-11 17:34:38 +0000823 print(snapclient.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000824
maruel@chromium.org75a59272010-06-11 22:34:03 +0000825 def root_dir(self):
826 return self._root_dir
827
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000828
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000829#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000830
831
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000832def CMDcleanup(parser, args):
833 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000834
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000835Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000836"""
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000837 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
838 help="override deps for the specified (comma-separated) "
839 "platform(s); 'all' will process all deps_os "
840 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000841 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000842 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000843 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000844 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000845 if options.verbose:
846 # Print out the .gclient file. This is longer than if we just printed the
847 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000848 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000849 return client.RunOnDeps('cleanup', args)
850
851
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000852@attr('usage', '[url] [safesync url]')
853def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000854 """Create a .gclient file in the current directory.
855
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000856This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000857top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000858modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000859provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000860URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000861"""
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000862 parser.add_option("--spec",
863 help="create a gclient file containing the provided "
864 "string. Due to Cygwin/Python brokenness, it "
865 "probably can't contain any newlines.")
866 parser.add_option("--name",
867 help="overrides the default name for the solution")
868 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000869 if ((options.spec and args) or len(args) > 2 or
870 (not options.spec and not args)):
871 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
872
maruel@chromium.org0329e672009-05-13 18:41:04 +0000873 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000874 raise gclient_utils.Error("%s file already exists in the current directory"
875 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000876 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000877 if options.spec:
878 client.SetConfig(options.spec)
879 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000880 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000881 if not options.name:
882 name = base_url.split("/")[-1]
883 else:
884 # specify an alternate relpath for the given URL.
885 name = options.name
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000886 safesync_url = ""
887 if len(args) > 1:
888 safesync_url = args[1]
889 client.SetDefaultConfig(name, base_url, safesync_url)
890 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000891 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000892
893
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000894def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000895 """Wrapper for svn export for all managed directories."""
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000896 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
897 help="override deps for the specified (comma-separated) "
898 "platform(s); 'all' will process all deps_os "
899 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000900 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000901 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000902 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000903 client = GClient.LoadCurrentConfig(options)
904
905 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000906 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000907
908 if options.verbose:
909 # Print out the .gclient file. This is longer than if we just printed the
910 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000911 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000912 return client.RunOnDeps('export', args)
913
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000914
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000915@attr('epilog', """Example:
916 gclient pack > patch.txt
917 generate simple patch for configured client and dependences
918""")
919def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000920 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000921
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000922Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000923dependencies, and performs minimal postprocessing of the output. The
924resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000925checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000926"""
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000927 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
928 help="override deps for the specified (comma-separated) "
929 "platform(s); 'all' will process all deps_os "
930 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000931 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000932 client = GClient.LoadCurrentConfig(options)
933 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000934 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +0000935 if options.verbose:
936 # Print out the .gclient file. This is longer than if we just printed the
937 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000938 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000939 return client.RunOnDeps('pack', args)
940
941
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000942def CMDstatus(parser, args):
943 """Show modification status for every dependencies."""
maruel@chromium.org6b1d00b2010-05-26 20:11:08 +0000944 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000945 help="override deps for the specified (comma-separated) "
946 "platform(s); 'all' will process all deps_os "
947 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000948 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000949 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000950 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000951 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000952 if options.verbose:
953 # Print out the .gclient file. This is longer than if we just printed the
954 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000955 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000956 return client.RunOnDeps('status', args)
957
958
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000959@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000960 gclient sync
961 update files from SCM according to current configuration,
962 *for modules which have changed since last update or sync*
963 gclient sync --force
964 update files from SCM according to current configuration, for
965 all modules (useful for recovering files deleted from local copy)
966 gclient sync --revision src@31000
967 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000968""")
969def CMDsync(parser, args):
970 """Checkout/update all modules."""
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000971 parser.add_option("-f", "--force", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000972 help="force update even for unchanged modules")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000973 parser.add_option("-n", "--nohooks", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000974 help="don't run hooks after the update is complete")
975 parser.add_option("-r", "--revision", action="append",
976 dest="revisions", metavar="REV", default=[],
maruel@chromium.org307d1792010-05-31 20:03:13 +0000977 help="Enforces revision/hash for the solutions with the "
978 "format src@rev. The src@ part is optional and can be "
979 "skipped. -r can be used multiple times when .gclient "
980 "has multiple solutions configured and will work even "
981 "if the src@ part is skipped.")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000982 parser.add_option("-H", "--head", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000983 help="skips any safesync_urls specified in "
984 "configured solutions and sync to head instead")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000985 parser.add_option("-D", "--delete_unversioned_trees", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000986 help="delete any unexpected unversioned trees "
987 "that are in the checkout")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000988 parser.add_option("-R", "--reset", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000989 help="resets any local changes before updating (git only)")
990 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000991 help="override deps for the specified (comma-separated) "
992 "platform(s); 'all' will process all deps_os "
993 "references")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000994 parser.add_option("-m", "--manually_grab_svn_rev", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000995 help="Skip svn up whenever possible by requesting "
996 "actual HEAD revision from the repository")
997 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000998 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000999
1000 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001001 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001002
maruel@chromium.org307d1792010-05-31 20:03:13 +00001003 if options.revisions and options.head:
1004 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
1005 print("Warning: you cannot use both --head and --revision")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001006
1007 if options.verbose:
1008 # Print out the .gclient file. This is longer than if we just printed the
1009 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001010 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001011 return client.RunOnDeps('update', args)
1012
1013
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001014def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001015 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001016 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001017
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001018def CMDdiff(parser, args):
1019 """Displays local diff for every dependencies."""
maruel@chromium.org86c0dec2010-05-28 19:01:00 +00001020 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
1021 help="override deps for the specified (comma-separated) "
1022 "platform(s); 'all' will process all deps_os "
1023 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001024 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001025 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001026 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001027 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001028 if options.verbose:
1029 # Print out the .gclient file. This is longer than if we just printed the
1030 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001031 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001032 return client.RunOnDeps('diff', args)
1033
1034
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001035def CMDrevert(parser, args):
1036 """Revert all modifications in every dependencies."""
maruel@chromium.org6b1d00b2010-05-26 20:11:08 +00001037 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
maruel@chromium.org86c0dec2010-05-28 19:01:00 +00001038 help="override deps for the specified (comma-separated) "
1039 "platform(s); 'all' will process all deps_os "
1040 "references")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +00001041 parser.add_option("-n", "--nohooks", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001042 help="don't run hooks after the revert is complete")
1043 (options, args) = parser.parse_args(args)
1044 # --force is implied.
1045 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001046 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001047 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001048 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049 return client.RunOnDeps('revert', args)
1050
1051
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001052def CMDrunhooks(parser, args):
1053 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org6b1d00b2010-05-26 20:11:08 +00001054 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
maruel@chromium.org86c0dec2010-05-28 19:01:00 +00001055 help="override deps for the specified (comma-separated) "
1056 "platform(s); 'all' will process all deps_os "
1057 "references")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +00001058 parser.add_option("-f", "--force", action="store_true", default=True,
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001059 help="Deprecated. No effect.")
1060 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001061 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001062 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001063 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064 if options.verbose:
1065 # Print out the .gclient file. This is longer than if we just printed the
1066 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001067 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001068 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001069 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001070 return client.RunOnDeps('runhooks', args)
1071
1072
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001073def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001074 """Output revision info mapping for the client and its dependencies.
1075
1076 This allows the capture of an overall "revision" for the source tree that
1077 can be used to reproduce the same tree in the future. It is only useful for
1078 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
1079 number or a git hash. A git branch name isn't "pinned" since the actual
1080 commit can change.
1081 """
1082 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1083 help='override deps for the specified (comma-separated) '
1084 'platform(s); \'all\' will process all deps_os '
1085 'references')
1086 parser.add_option('-s', '--snapshot', action='store_true',
1087 help='creates a snapshot .gclient file of the current '
1088 'version of all repositories to reproduce the tree, '
1089 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001090 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001091 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001093 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001095 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001096
1097
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001098def Command(name):
1099 return getattr(sys.modules[__name__], 'CMD' + name, None)
1100
1101
1102def CMDhelp(parser, args):
1103 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001104 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001105 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001106 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001107 parser.print_help()
1108 return 0
1109
1110
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001111def GenUsage(parser, command):
1112 """Modify an OptParse object with the function's documentation."""
1113 obj = Command(command)
1114 if command == 'help':
1115 command = '<command>'
1116 # OptParser.description prefer nicely non-formatted strings.
1117 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1118 usage = getattr(obj, 'usage', '')
1119 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1120 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001121
1122
1123def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001124 """Doesn't parse the arguments here, just find the right subcommand to
1125 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001126 try:
1127 # Do it late so all commands are listed.
1128 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1129 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1130 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1131 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001132 parser.add_option('-v', '--verbose', action='count', default=0,
1133 help='Produces additional output for diagnostics. Can be '
1134 'used up to three times for more logging info.')
1135 parser.add_option('--gclientfile', dest='config_filename',
1136 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1137 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001138 # Integrate standard options processing.
1139 old_parser = parser.parse_args
1140 def Parse(args):
1141 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001142 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001143 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001144 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001145 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001146 level = logging.DEBUG
1147 logging.basicConfig(level=level,
1148 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1149 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001150 if not hasattr(options, 'revisions'):
1151 # GClient.RunOnDeps expects it even if not applicable.
1152 options.revisions = []
1153 if not hasattr(options, 'head'):
1154 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001155 if not hasattr(options, 'nohooks'):
1156 options.nohooks = True
1157 if not hasattr(options, 'deps_os'):
1158 options.deps_os = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001159 return (options, args)
1160 parser.parse_args = Parse
1161 # We don't want wordwrapping in epilog (usually examples)
1162 parser.format_epilog = lambda _: parser.epilog or ''
1163 if argv:
1164 command = Command(argv[0])
1165 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001166 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001167 GenUsage(parser, argv[0])
1168 return command(parser, argv[1:])
1169 # Not a known command. Default to help.
1170 GenUsage(parser, 'help')
1171 return CMDhelp(parser, argv)
1172 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001173 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001174 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001175
1176
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001177if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001178 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001179
1180# vim: ts=2:sw=2:tw=80:et: