blob: 96eee3ac4f51a14a23a64d8962fdc49cce124e32 [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.orgdf2b3152010-07-21 17:35:24 +000052__version__ = "0.5"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org754960e2009-09-21 12:31:05 +000054import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000055import optparse
56import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000057import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000058import re
piman@chromium.org4b90e3a2010-07-01 20:28:26 +000059import subprocess
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urllib
63
maruel@chromium.orgada4c652009-12-03 15:32:01 +000064import breakpad
65
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000066import gclient_scm
67import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000068from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000069
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000071def attr(attr, data):
72 """Sets an attribute on a function."""
73 def hook(fn):
74 setattr(fn, attr, data)
75 return fn
76 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000077
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079## GClient implementation.
80
81
maruel@chromium.org116704f2010-06-11 17:34:38 +000082class GClientKeywords(object):
83 class FromImpl(object):
84 """Used to implement the From() syntax."""
85
86 def __init__(self, module_name, sub_target_name=None):
87 """module_name is the dep module we want to include from. It can also be
88 the name of a subdirectory to include from.
89
90 sub_target_name is an optional parameter if the module name in the other
91 DEPS file is different. E.g., you might want to map src/net to net."""
92 self.module_name = module_name
93 self.sub_target_name = sub_target_name
94
95 def __str__(self):
96 return 'From(%s, %s)' % (repr(self.module_name),
97 repr(self.sub_target_name))
98
maruel@chromium.org116704f2010-06-11 17:34:38 +000099 class FileImpl(object):
100 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000101 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000102
103 def __init__(self, file_location):
104 self.file_location = file_location
105
106 def __str__(self):
107 return 'File("%s")' % self.file_location
108
109 def GetPath(self):
110 return os.path.split(self.file_location)[0]
111
112 def GetFilename(self):
113 rev_tokens = self.file_location.split('@')
114 return os.path.split(rev_tokens[0])[1]
115
116 def GetRevision(self):
117 rev_tokens = self.file_location.split('@')
118 if len(rev_tokens) > 1:
119 return rev_tokens[1]
120 return None
121
122 class VarImpl(object):
123 def __init__(self, custom_vars, local_scope):
124 self._custom_vars = custom_vars
125 self._local_scope = local_scope
126
127 def Lookup(self, var_name):
128 """Implements the Var syntax."""
129 if var_name in self._custom_vars:
130 return self._custom_vars[var_name]
131 elif var_name in self._local_scope.get("vars", {}):
132 return self._local_scope["vars"][var_name]
133 raise gclient_utils.Error("Var is not defined: %s" % var_name)
134
135
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000136class Dependency(GClientKeywords):
137 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000138 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000139
maruel@chromium.org0d812442010-08-10 12:41:08 +0000140 def __init__(self, parent, name, url, safesync_url, custom_deps,
141 custom_vars, deps_file):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000142 GClientKeywords.__init__(self)
143 self.parent = parent
144 self.name = name
145 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000146 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000147 # These 2 are only set in .gclient and not in DEPS files.
148 self.safesync_url = safesync_url
149 self.custom_vars = custom_vars or {}
150 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000151 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000152 self.dependencies = []
153 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000154 # A cache of the files affected by the current operation, necessary for
155 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000156 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000157 # If it is not set to True, the dependency wasn't processed for its child
158 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000159 self.deps_parsed = False
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000160 # A direct reference is dependency that is referenced by a deps, deps_os or
161 # solution. A indirect one is one that was loaded with From() or that
162 # exceeded recursion limit.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000163 self.direct_reference = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000164 # This dependency has been processed, i.e. checked out
165 self.processed = False
166 # This dependency had its hook run
167 self.hooks_ran = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000168
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000169 # Sanity checks
170 if not self.name and self.parent:
171 raise gclient_utils.Error('Dependency without name')
172 if not isinstance(self.url,
173 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
174 raise gclient_utils.Error('dependency url must be either a string, None, '
175 'File() or From() instead of %s' %
176 self.url.__class__.__name__)
177 if '/' in self.deps_file or '\\' in self.deps_file:
178 raise gclient_utils.Error('deps_file name must not be a path, just a '
179 'filename. %s' % self.deps_file)
180
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000181 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000182 """Resolves the parsed url from url.
183
184 Manages From() keyword accordingly. Do not touch self.parsed_url nor
185 self.url because it may called with other urls due to From()."""
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000186 overriden_url = self.get_custom_deps(self.name, url)
187 if overriden_url != url:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000188 logging.debug('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000189 overriden_url))
190 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000191 elif isinstance(url, self.FromImpl):
192 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000193 if not ref:
194 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
195 url.module_name, ref))
196 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000197 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000198 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000199 # Make sure the referenced dependency DEPS file is loaded and file the
200 # inner referenced dependency.
201 ref.ParseDepsFile(False)
202 found_dep = None
203 for d in ref.dependencies:
204 if d.name == sub_target:
205 found_dep = d
206 break
207 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000208 raise gclient_utils.Error(
209 'Couldn\'t find %s in %s, referenced by %s' % (
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000210 sub_target, ref.name, self.name))
211 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000212 parsed_url = found_dep.LateOverride(found_dep.url)
213 logging.debug('%s, %s to %s' % (self.name, url, parsed_url))
214 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000215 elif isinstance(url, basestring):
216 parsed_url = urlparse.urlparse(url)
217 if not parsed_url[0]:
218 # A relative url. Fetch the real base.
219 path = parsed_url[2]
220 if not path.startswith('/'):
221 raise gclient_utils.Error(
222 'relative DEPS entry \'%s\' must begin with a slash' % url)
223 # Create a scm just to query the full url.
224 parent_url = self.parent.parsed_url
225 if isinstance(parent_url, self.FileImpl):
226 parent_url = parent_url.file_location
227 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000228 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000229 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000230 parsed_url = url
231 logging.debug('%s, %s -> %s' % (self.name, url, parsed_url))
232 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000233 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000234 parsed_url = url
235 logging.debug('%s, %s -> %s (File)' % (self.name, url, parsed_url))
236 return parsed_url
237 elif url is None:
238 return None
239 else:
240 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000241
maruel@chromium.org271375b2010-06-23 19:17:38 +0000242 def ParseDepsFile(self, direct_reference):
243 """Parses the DEPS file for this dependency."""
244 if direct_reference:
245 # Maybe it was referenced earlier by a From() keyword but it's now
246 # directly referenced.
247 self.direct_reference = direct_reference
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000248 if self.deps_parsed:
249 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000250 self.deps_parsed = True
251 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
252 if not os.path.isfile(filepath):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000253 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000254 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000255
maruel@chromium.org271375b2010-06-23 19:17:38 +0000256 # Eval the content.
257 # One thing is unintuitive, vars= {} must happen before Var() use.
258 local_scope = {}
259 var = self.VarImpl(self.custom_vars, local_scope)
260 global_scope = {
261 'File': self.FileImpl,
262 'From': self.FromImpl,
263 'Var': var.Lookup,
264 'deps_os': {},
265 }
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000266 try:
267 exec(deps_content, global_scope, local_scope)
268 except SyntaxError, e:
269 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000270 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000271 # load os specific dependencies if defined. these dependencies may
272 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000273 if 'deps_os' in local_scope:
274 for deps_os_key in self.enforced_os():
275 os_deps = local_scope['deps_os'].get(deps_os_key, {})
276 if len(self.enforced_os()) > 1:
277 # Ignore any conflict when including deps for more than one
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000278 # platform, so we collect the broadest set of dependencies available.
279 # We may end up with the wrong revision of something for our
280 # platform, but this is the best we can do.
281 deps.update([x for x in os_deps.items() if not x[0] in deps])
282 else:
283 deps.update(os_deps)
284
maruel@chromium.org271375b2010-06-23 19:17:38 +0000285 self.deps_hooks.extend(local_scope.get('hooks', []))
286
287 # If a line is in custom_deps, but not in the solution, we want to append
288 # this line to the solution.
289 for d in self.custom_deps:
290 if d not in deps:
291 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000292
293 # If use_relative_paths is set in the DEPS file, regenerate
294 # the dictionary using paths relative to the directory containing
295 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000296 use_relative_paths = local_scope.get('use_relative_paths', False)
297 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000298 rel_deps = {}
299 for d, url in deps.items():
300 # normpath is required to allow DEPS to use .. in their
301 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000302 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
303 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000304
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000305 # Convert the deps into real Dependency.
306 for name, url in deps.iteritems():
307 if name in [s.name for s in self.dependencies]:
308 raise
maruel@chromium.org0d812442010-08-10 12:41:08 +0000309 self.dependencies.append(Dependency(self, name, url, None, None, None,
310 None))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000311 # Sorting by name would in theory make the whole thing coherent, since
312 # subdirectories will be sorted after the parent directory, but that doens't
313 # work with From() that fetch from a dependency with a name being sorted
314 # later. But if this would be removed right now, many projects wouldn't be
315 # able to sync anymore.
bradnelson@google.com5f8f2a82010-07-23 00:05:46 +0000316 self.dependencies.sort(key=lambda x: x.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000317 logging.info('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000318
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000319 def RunCommandRecursively(self, options, revision_overrides,
320 command, args, pm):
321 """Runs 'command' before parsing the DEPS in case it's a initial checkout
322 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000323 assert self._file_list == []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000324 # When running runhooks, there's no need to consult the SCM.
325 # All known hooks are expected to run unconditionally regardless of working
326 # copy state, so skip the SCM status check.
327 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000328 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000329 if run_scm and self.parsed_url:
330 if isinstance(self.parsed_url, self.FileImpl):
331 # Special support for single-file checkout.
332 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
333 options.revision = self.parsed_url.GetRevision()
334 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
335 self.root_dir(),
336 self.name)
337 scm.RunCommand('updatesingle', options,
338 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000339 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000340 else:
341 options.revision = revision_overrides.get(self.name)
342 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000343 scm.RunCommand(command, options, args, self._file_list)
344 self._file_list = [os.path.join(self.name, f.strip())
345 for f in self._file_list]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000346 options.revision = None
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000347 self.processed = True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000348 if pm:
349 # The + 1 comes from the fact that .gclient is considered a step in
350 # itself, .i.e. this code is called one time for the .gclient. This is not
351 # conceptually correct but it simplifies code.
352 pm._total = len(self.tree(False)) + 1
353 pm.update()
354 if self.recursion_limit():
355 # Then we can parse the DEPS file.
356 self.ParseDepsFile(True)
357 if pm:
358 pm._total = len(self.tree(False)) + 1
359 pm.update(0)
360 # Parse the dependencies of this dependency.
361 for s in self.dependencies:
362 # TODO(maruel): All these can run concurrently! No need for threads,
363 # just buffer stdout&stderr on pipes and flush as they complete.
364 # Watch out for stdin.
365 s.RunCommandRecursively(options, revision_overrides, command, args, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000366
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000367 def RunHooksRecursively(self, options):
368 """Evaluates all hooks, running actions as needed. RunCommandRecursively()
369 must have been called before to load the DEPS."""
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000370 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000371 # changed.
372 if self.deps_hooks and self.direct_reference:
373 # TODO(maruel): If the user is using git or git-svn, then we don't know
374 # what files have changed so we always run all hooks. It'd be nice to fix
375 # that.
376 if (options.force or
377 isinstance(self.parsed_url, self.FileImpl) or
378 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
379 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
380 for hook_dict in self.deps_hooks:
381 self._RunHookAction(hook_dict, [])
382 else:
383 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
384 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000385 file_list = self.file_list()
386 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000387 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000388 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000389 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000390
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000391 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000392 file_list[i].lower()])
393 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000394
395 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000396 while (file_list[i].startswith('\\') or
397 file_list[i].startswith('/')):
398 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000399
400 # Run hooks on the basis of whether the files from the gclient operation
401 # match each hook's pattern.
402 for hook_dict in self.deps_hooks:
403 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000404 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000405 if matching_file_list:
406 self._RunHookAction(hook_dict, matching_file_list)
407 if self.recursion_limit():
408 for s in self.dependencies:
409 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000410
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000411 def _RunHookAction(self, hook_dict, matching_file_list):
412 """Runs the action from a single hook."""
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000413 self.hooks_ran = True
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000414 logging.info(hook_dict)
415 logging.info(matching_file_list)
416 command = hook_dict['action'][:]
417 if command[0] == 'python':
418 # If the hook specified "python" as the first item, the action is a
419 # Python script. Run it by starting a new copy of the same
420 # interpreter.
421 command[0] = sys.executable
422
423 if '$matching_files' in command:
424 splice_index = command.index('$matching_files')
425 command[splice_index:splice_index + 1] = matching_file_list
426
427 # Use a discrete exit status code of 2 to indicate that a hook action
428 # failed. Users of this script may wish to treat hook action failures
429 # differently from VC failures.
430 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
431
maruel@chromium.org271375b2010-06-23 19:17:38 +0000432 def root_dir(self):
433 return self.parent.root_dir()
434
435 def enforced_os(self):
436 return self.parent.enforced_os()
437
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000438 def recursion_limit(self):
439 return self.parent.recursion_limit() - 1
440
maruel@chromium.org0d812442010-08-10 12:41:08 +0000441 def tree(self, include_all):
442 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000443
maruel@chromium.org0d812442010-08-10 12:41:08 +0000444 def subtree(self, include_all):
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000445 result = []
446 # Add breadth-first.
maruel@chromium.org0d812442010-08-10 12:41:08 +0000447 if self.direct_reference or include_all:
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000448 for d in self.dependencies:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000449 result.append(d)
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000450 for d in self.dependencies:
maruel@chromium.org0d812442010-08-10 12:41:08 +0000451 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000452 return result
453
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000454 def get_custom_deps(self, name, url):
455 """Returns a custom deps if applicable."""
456 if self.parent:
457 url = self.parent.get_custom_deps(name, url)
458 # None is a valid return value to disable a dependency.
459 return self.custom_deps.get(name, url)
460
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000461 def file_list(self):
462 result = self._file_list[:]
463 for d in self.dependencies:
464 result.extend(d.file_list())
465 return result
466
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000467 def __str__(self):
468 out = []
469 for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars',
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000470 'deps_hooks', '_file_list', 'processed',
471 'hooks_ran'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000472 # 'deps_file'
473 if self.__dict__[i]:
474 out.append('%s: %s' % (i, self.__dict__[i]))
475
476 for d in self.dependencies:
477 out.extend([' ' + x for x in str(d).splitlines()])
478 out.append('')
479 return '\n'.join(out)
480
481 def __repr__(self):
482 return '%s: %s' % (self.name, self.url)
483
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000484 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000485 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000486 out = '%s(%s)' % (self.name, self.url)
487 i = self.parent
488 while i and i.name:
489 out = '%s(%s) -> %s' % (i.name, i.url, out)
490 i = i.parent
491 return out
492
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000493
494class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000495 """Object that represent a gclient checkout. A tree of Dependency(), one per
496 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000497
498 DEPS_OS_CHOICES = {
499 "win32": "win",
500 "win": "win",
501 "cygwin": "win",
502 "darwin": "mac",
503 "mac": "mac",
504 "unix": "unix",
505 "linux": "unix",
506 "linux2": "unix",
507 }
508
509 DEFAULT_CLIENT_FILE_TEXT = ("""\
510solutions = [
511 { "name" : "%(solution_name)s",
512 "url" : "%(solution_url)s",
513 "custom_deps" : {
514 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000515 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000516 },
517]
518""")
519
520 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
521 { "name" : "%(solution_name)s",
522 "url" : "%(solution_url)s",
523 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000524%(solution_deps)s },
525 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000526 },
527""")
528
529 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
530# Snapshot generated with gclient revinfo --snapshot
531solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000532%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000533""")
534
535 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000536 # Do not change previous behavior. Only solution level and immediate DEPS
537 # are processed.
538 self._recursion_limit = 2
539 Dependency.__init__(self, None, None, None, None, None, None, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000540 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000541 if options.deps_os:
542 enforced_os = options.deps_os.split(',')
543 else:
544 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
545 if 'all' in enforced_os:
546 enforced_os = self.DEPS_OS_CHOICES.itervalues()
547 self._enforced_os = list(set(enforced_os))
548 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000549 self.config_content = None
550
551 def SetConfig(self, content):
552 assert self.dependencies == []
553 config_dict = {}
554 self.config_content = content
555 try:
556 exec(content, config_dict)
557 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000558 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000559 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000560 try:
561 self.dependencies.append(Dependency(
562 self, s['name'], s['url'],
563 s.get('safesync_url', None),
564 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000565 s.get('custom_vars', {}),
566 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000567 except KeyError:
568 raise gclient_utils.Error('Invalid .gclient file. Solution is '
569 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000570 # .gclient can have hooks.
571 self.deps_hooks = config_dict.get('hooks', [])
572
573 def SaveConfig(self):
574 gclient_utils.FileWrite(os.path.join(self.root_dir(),
575 self._options.config_filename),
576 self.config_content)
577
578 @staticmethod
579 def LoadCurrentConfig(options):
580 """Searches for and loads a .gclient file relative to the current working
581 dir. Returns a GClient object."""
582 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
583 if not path:
584 return None
585 client = GClient(path, options)
586 client.SetConfig(gclient_utils.FileRead(
587 os.path.join(path, options.config_filename)))
588 return client
589
590 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
591 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
592 'solution_name': solution_name,
593 'solution_url': solution_url,
594 'safesync_url' : safesync_url,
595 })
596
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000597 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000598 """Creates a .gclient_entries file to record the list of unique checkouts.
599
600 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000601 """
602 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
603 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000604 result = 'entries = {\n'
605 for entry in self.tree(False):
606 # Skip over File() dependencies as we can't version them.
607 if not isinstance(entry.parsed_url, self.FileImpl):
608 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
609 pprint.pformat(entry.parsed_url))
610 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000611 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000612 logging.info(result)
613 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000614
615 def _ReadEntries(self):
616 """Read the .gclient_entries file for the given client.
617
618 Returns:
619 A sequence of solution names, which will be empty if there is the
620 entries file hasn't been created yet.
621 """
622 scope = {}
623 filename = os.path.join(self.root_dir(), self._options.entries_filename)
624 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000625 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000626 try:
627 exec(gclient_utils.FileRead(filename), scope)
628 except SyntaxError, e:
629 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000630 return scope['entries']
631
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000632 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000633 """Checks for revision overrides."""
634 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000635 if self._options.head:
636 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000637 for s in self.dependencies:
638 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000639 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000640 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000641 rev = handle.read().strip()
642 handle.close()
643 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000644 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000645 if not self._options.revisions:
646 return revision_overrides
647 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000648 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000649 index = 0
650 for revision in self._options.revisions:
651 if not '@' in revision:
652 # Support for --revision 123
653 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000654 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000655 if not sol in solutions_names:
656 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
657 print >> sys.stderr, ('Please fix your script, having invalid '
658 '--revision flags will soon considered an error.')
659 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000660 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000661 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000662 return revision_overrides
663
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000664 def RunOnDeps(self, command, args):
665 """Runs a command on each dependency in a client and its dependencies.
666
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000667 Args:
668 command: The command to use (e.g., 'status' or 'diff')
669 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000670 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000671 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000672 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000673 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000674 pm = None
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000675 if command == 'update' and not self._options.verbose:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000676 pm = Progress('Syncing projects', len(self.tree(False)) + 1)
677 self.RunCommandRecursively(self._options, revision_overrides,
678 command, args, pm)
679 if pm:
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000680 pm.end()
piman@chromium.org6f363722010-04-27 00:41:09 +0000681
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000682 # Once all the dependencies have been processed, it's now safe to run the
683 # hooks.
684 if not self._options.nohooks:
685 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000686
687 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000688 # Notify the user if there is an orphaned entry in their working copy.
689 # Only delete the directory if there are no changes in it, and
690 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000691 entries = [i.name for i in self.tree(False)]
692 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000693 # Fix path separator on Windows.
694 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000695 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000696 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000697 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000698 file_list = []
699 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
700 scm.status(self._options, [], file_list)
701 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000702 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000703 # There are modified files in this entry. Keep warning until
704 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000705 print(('\nWARNING: \'%s\' is no longer part of this client. '
706 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000707 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000708 else:
709 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000710 print('\n________ deleting \'%s\' in \'%s\'' % (
711 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000712 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000713 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000714 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000715 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000716
717 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000718 """Output revision info mapping for the client and its dependencies.
719
720 This allows the capture of an overall "revision" for the source tree that
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000721 can be used to reproduce the same tree in the future. It is only useful for
722 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
723 number or a git hash. A git branch name isn't "pinned" since the actual
724 commit can change.
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000725
726 The --snapshot option allows creating a .gclient file to reproduce the tree.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000727 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000728 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000729 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000730 # Load all the settings.
731 self.RunCommandRecursively(self._options, {}, None, [], None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000732
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000733 def GetURLAndRev(name, original_url):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000734 """Returns the revision-qualified SCM url."""
735 if original_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000736 return None
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000737 if isinstance(original_url, self.FileImpl):
738 original_url = original_url.file_location
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000739 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000740 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000741 if not os.path.isdir(scm.checkout_path):
742 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000743 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000744
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000745 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000746 new_gclient = ''
747 # First level at .gclient
748 for d in self.dependencies:
749 entries = {}
750 def GrabDeps(sol):
751 """Recursively grab dependencies."""
752 for i in sol.dependencies:
753 entries[i.name] = GetURLAndRev(i.name, i.parsed_url)
754 GrabDeps(i)
755 GrabDeps(d)
756 custom_deps = []
757 for k in sorted(entries.keys()):
758 if entries[k]:
759 # Quotes aren't escaped...
760 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
761 else:
762 custom_deps.append(' \"%s\": None,\n' % k)
763 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
764 'solution_name': d.name,
765 'solution_url': d.url,
766 'safesync_url' : d.safesync_url or '',
767 'solution_deps': ''.join(custom_deps),
768 }
769 # Print the snapshot configuration file
770 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000771 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000772 entries = sorted(self.tree(False), key=lambda i: i.name)
773 for entry in entries:
774 entry_url = GetURLAndRev(entry.name, entry.parsed_url)
775 line = '%s: %s' % (entry.name, entry_url)
776 if not entry is entries[-1]:
777 line += ';'
778 print line
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000779 logging.debug(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000780
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000781 def ParseDepsFile(self, direct_reference):
782 """No DEPS to parse for a .gclient file."""
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000783 self.direct_reference = True
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000784 self.deps_parsed = True
785
maruel@chromium.org75a59272010-06-11 22:34:03 +0000786 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000787 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000788 return self._root_dir
789
maruel@chromium.org271375b2010-06-23 19:17:38 +0000790 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000791 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000792 return self._enforced_os
793
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000794 def recursion_limit(self):
795 """How recursive can each dependencies in DEPS file can load DEPS file."""
796 return self._recursion_limit
797
maruel@chromium.org0d812442010-08-10 12:41:08 +0000798 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000799 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000800 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000801
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000802
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000803#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000804
805
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000806def CMDcleanup(parser, args):
807 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000808
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000809Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000810"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000811 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
812 help='override deps for the specified (comma-separated) '
813 'platform(s); \'all\' will process all deps_os '
814 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000815 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000816 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000817 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000818 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000819 if options.verbose:
820 # Print out the .gclient file. This is longer than if we just printed the
821 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000822 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823 return client.RunOnDeps('cleanup', args)
824
825
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000826@attr('usage', '[command] [args ...]')
827def CMDrecurse(parser, args):
828 """Operates on all the entries.
829
830 Runs a shell command on all entries.
831 """
832 # Stop parsing at the first non-arg so that these go through to the command
833 parser.disable_interspersed_args()
834 parser.add_option('-s', '--scm', action='append', default=[],
835 help='choose scm types to operate upon')
836 options, args = parser.parse_args(args)
837 root, entries = gclient_utils.GetGClientRootAndEntries()
838 scm_set = set()
839 for scm in options.scm:
840 scm_set.update(scm.split(','))
841
842 # Pass in the SCM type as an env variable
843 env = os.environ.copy()
844
845 for path, url in entries.iteritems():
846 scm = gclient_scm.GetScmName(url)
847 if scm_set and scm not in scm_set:
848 continue
849 dir = os.path.normpath(os.path.join(root, path))
850 env['GCLIENT_SCM'] = scm
851 env['GCLIENT_URL'] = url
852 subprocess.Popen(args, cwd=dir, env=env).communicate()
853
854
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000855@attr('usage', '[url] [safesync url]')
856def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000857 """Create a .gclient file in the current directory.
858
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000859This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000860top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000861modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000862provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000863URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000864"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000865 parser.add_option('--spec',
866 help='create a gclient file containing the provided '
867 'string. Due to Cygwin/Python brokenness, it '
868 'probably can\'t contain any newlines.')
869 parser.add_option('--name',
870 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000871 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000872 if ((options.spec and args) or len(args) > 2 or
873 (not options.spec and not args)):
874 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
875
maruel@chromium.org0329e672009-05-13 18:41:04 +0000876 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000877 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000878 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000879 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000880 if options.spec:
881 client.SetConfig(options.spec)
882 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000883 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000884 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000885 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000886 else:
887 # specify an alternate relpath for the given URL.
888 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000889 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000890 if len(args) > 1:
891 safesync_url = args[1]
892 client.SetDefaultConfig(name, base_url, safesync_url)
893 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000894 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000895
896
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000897def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000898 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000899 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
900 help='override deps for the specified (comma-separated) '
901 'platform(s); \'all\' will process all deps_os '
902 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000903 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000904 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000905 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000906 client = GClient.LoadCurrentConfig(options)
907
908 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000909 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000910
911 if options.verbose:
912 # Print out the .gclient file. This is longer than if we just printed the
913 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000914 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000915 return client.RunOnDeps('export', args)
916
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000917
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000918@attr('epilog', """Example:
919 gclient pack > patch.txt
920 generate simple patch for configured client and dependences
921""")
922def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000923 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000924
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000925Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000926dependencies, and performs minimal postprocessing of the output. The
927resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000928checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000929"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000930 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
931 help='override deps for the specified (comma-separated) '
932 'platform(s); \'all\' will process all deps_os '
933 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000934 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000935 client = GClient.LoadCurrentConfig(options)
936 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000937 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000938 if options.verbose:
939 # Print out the .gclient file. This is longer than if we just printed the
940 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000941 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000942 return client.RunOnDeps('pack', args)
943
944
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000945def CMDstatus(parser, args):
946 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000947 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
948 help='override deps for the specified (comma-separated) '
949 'platform(s); \'all\' will process all deps_os '
950 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000951 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000952 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000953 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000954 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000955 if options.verbose:
956 # Print out the .gclient file. This is longer than if we just printed the
957 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000958 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000959 return client.RunOnDeps('status', args)
960
961
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000962@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000963 gclient sync
964 update files from SCM according to current configuration,
965 *for modules which have changed since last update or sync*
966 gclient sync --force
967 update files from SCM according to current configuration, for
968 all modules (useful for recovering files deleted from local copy)
969 gclient sync --revision src@31000
970 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000971""")
972def CMDsync(parser, args):
973 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000974 parser.add_option('-f', '--force', action='store_true',
975 help='force update even for unchanged modules')
976 parser.add_option('-n', '--nohooks', action='store_true',
977 help='don\'t run hooks after the update is complete')
978 parser.add_option('-r', '--revision', action='append',
979 dest='revisions', metavar='REV', default=[],
980 help='Enforces revision/hash for the solutions with the '
981 'format src@rev. The src@ part is optional and can be '
982 'skipped. -r can be used multiple times when .gclient '
983 'has multiple solutions configured and will work even '
984 'if the src@ part is skipped.')
985 parser.add_option('-H', '--head', action='store_true',
986 help='skips any safesync_urls specified in '
987 'configured solutions and sync to head instead')
988 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
989 help='delete any unexpected unversioned trees '
990 'that are in the checkout')
991 parser.add_option('-R', '--reset', action='store_true',
992 help='resets any local changes before updating (git only)')
993 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
994 help='override deps for the specified (comma-separated) '
995 'platform(s); \'all\' will process all deps_os '
996 'references')
997 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
998 help='Skip svn up whenever possible by requesting '
999 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001000 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001001 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001002
1003 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001004 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001005
maruel@chromium.org307d1792010-05-31 20:03:13 +00001006 if options.revisions and options.head:
1007 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001008 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001009
1010 if options.verbose:
1011 # Print out the .gclient file. This is longer than if we just printed the
1012 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001013 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001014 return client.RunOnDeps('update', args)
1015
1016
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001017def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001018 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001019 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001020
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001021def CMDdiff(parser, args):
1022 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001023 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1024 help='override deps for the specified (comma-separated) '
1025 'platform(s); \'all\' will process all deps_os '
1026 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001027 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001028 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001029 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001030 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001031 if options.verbose:
1032 # Print out the .gclient file. This is longer than if we just printed the
1033 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001034 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001035 return client.RunOnDeps('diff', args)
1036
1037
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001038def CMDrevert(parser, args):
1039 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001040 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1041 help='override deps for the specified (comma-separated) '
1042 'platform(s); \'all\' will process all deps_os '
1043 'references')
1044 parser.add_option('-n', '--nohooks', action='store_true',
1045 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001046 (options, args) = parser.parse_args(args)
1047 # --force is implied.
1048 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001049 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001050 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001051 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001052 return client.RunOnDeps('revert', args)
1053
1054
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001055def CMDrunhooks(parser, args):
1056 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001057 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1058 help='override deps for the specified (comma-separated) '
1059 'platform(s); \'all\' will process all deps_os '
1060 'references')
1061 parser.add_option('-f', '--force', action='store_true', default=True,
1062 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001063 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001064 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001065 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001066 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001067 if options.verbose:
1068 # Print out the .gclient file. This is longer than if we just printed the
1069 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001070 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001071 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001072 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001073 return client.RunOnDeps('runhooks', args)
1074
1075
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001076def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001077 """Output revision info mapping for the client and its dependencies.
1078
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001079 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001080 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001081 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1082 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001083 commit can change.
1084 """
1085 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1086 help='override deps for the specified (comma-separated) '
1087 'platform(s); \'all\' will process all deps_os '
1088 'references')
1089 parser.add_option('-s', '--snapshot', action='store_true',
1090 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001091 'version of all repositories to reproduce the tree')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001092 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001093 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001095 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001096 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001097 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001098
1099
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100def Command(name):
1101 return getattr(sys.modules[__name__], 'CMD' + name, None)
1102
1103
1104def CMDhelp(parser, args):
1105 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001106 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001107 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001108 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001109 parser.print_help()
1110 return 0
1111
1112
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001113def GenUsage(parser, command):
1114 """Modify an OptParse object with the function's documentation."""
1115 obj = Command(command)
1116 if command == 'help':
1117 command = '<command>'
1118 # OptParser.description prefer nicely non-formatted strings.
1119 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1120 usage = getattr(obj, 'usage', '')
1121 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1122 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001123
1124
1125def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001126 """Doesn't parse the arguments here, just find the right subcommand to
1127 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001128 try:
1129 # Do it late so all commands are listed.
1130 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1131 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1132 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1133 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001134 parser.add_option('-v', '--verbose', action='count', default=0,
1135 help='Produces additional output for diagnostics. Can be '
1136 'used up to three times for more logging info.')
1137 parser.add_option('--gclientfile', dest='config_filename',
1138 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1139 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001140 # Integrate standard options processing.
1141 old_parser = parser.parse_args
1142 def Parse(args):
1143 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001144 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001145 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001146 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001147 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001148 level = logging.DEBUG
1149 logging.basicConfig(level=level,
1150 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1151 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001152
1153 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001154 if not hasattr(options, 'revisions'):
1155 # GClient.RunOnDeps expects it even if not applicable.
1156 options.revisions = []
1157 if not hasattr(options, 'head'):
1158 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001159 if not hasattr(options, 'nohooks'):
1160 options.nohooks = True
1161 if not hasattr(options, 'deps_os'):
1162 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001163 if not hasattr(options, 'manually_grab_svn_rev'):
1164 options.manually_grab_svn_rev = None
1165 if not hasattr(options, 'force'):
1166 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001167 return (options, args)
1168 parser.parse_args = Parse
1169 # We don't want wordwrapping in epilog (usually examples)
1170 parser.format_epilog = lambda _: parser.epilog or ''
1171 if argv:
1172 command = Command(argv[0])
1173 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001174 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001175 GenUsage(parser, argv[0])
1176 return command(parser, argv[1:])
1177 # Not a known command. Default to help.
1178 GenUsage(parser, 'help')
1179 return CMDhelp(parser, argv)
1180 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001181 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001182 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001183
1184
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001185if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001186 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001187
1188# vim: ts=2:sw=2:tw=80:et: