blob: 8a848cf20b9a082e634cfb8cf747903cbc6c99e3 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# 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.org36ac2392011-10-12 16:36:11 +000052__version__ = "0.6.3"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000054import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000055import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056import optparse
57import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000058import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000059import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urllib
64
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000065import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066
maruel@chromium.org35625c72011-03-23 17:34:02 +000067import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000068import gclient_scm
69import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000070from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000071import subprocess2
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000074def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000075 """Sets an attribute on a function."""
76 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000077 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000078 return fn
79 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082## GClient implementation.
83
84
maruel@chromium.org116704f2010-06-11 17:34:38 +000085class GClientKeywords(object):
86 class FromImpl(object):
87 """Used to implement the From() syntax."""
88
89 def __init__(self, module_name, sub_target_name=None):
90 """module_name is the dep module we want to include from. It can also be
91 the name of a subdirectory to include from.
92
93 sub_target_name is an optional parameter if the module name in the other
94 DEPS file is different. E.g., you might want to map src/net to net."""
95 self.module_name = module_name
96 self.sub_target_name = sub_target_name
97
98 def __str__(self):
99 return 'From(%s, %s)' % (repr(self.module_name),
100 repr(self.sub_target_name))
101
maruel@chromium.org116704f2010-06-11 17:34:38 +0000102 class FileImpl(object):
103 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000104 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000105
106 def __init__(self, file_location):
107 self.file_location = file_location
108
109 def __str__(self):
110 return 'File("%s")' % self.file_location
111
112 def GetPath(self):
113 return os.path.split(self.file_location)[0]
114
115 def GetFilename(self):
116 rev_tokens = self.file_location.split('@')
117 return os.path.split(rev_tokens[0])[1]
118
119 def GetRevision(self):
120 rev_tokens = self.file_location.split('@')
121 if len(rev_tokens) > 1:
122 return rev_tokens[1]
123 return None
124
125 class VarImpl(object):
126 def __init__(self, custom_vars, local_scope):
127 self._custom_vars = custom_vars
128 self._local_scope = local_scope
129
130 def Lookup(self, var_name):
131 """Implements the Var syntax."""
132 if var_name in self._custom_vars:
133 return self._custom_vars[var_name]
134 elif var_name in self._local_scope.get("vars", {}):
135 return self._local_scope["vars"][var_name]
136 raise gclient_utils.Error("Var is not defined: %s" % var_name)
137
138
maruel@chromium.org064186c2011-09-27 23:53:33 +0000139class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000140 """Immutable configuration settings."""
141 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000142 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000143 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000144 GClientKeywords.__init__(self)
145
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000146 # These are not mutable:
147 self._parent = parent
148 self._safesync_url = safesync_url
149 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000150 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000151 # 'managed' determines whether or not this dependency is synced/updated by
152 # gclient after gclient checks it out initially. The difference between
153 # 'managed' and 'should_process' is that the user specifies 'managed' via
154 # the --unmanaged command-line flag or a .gclient config, where
155 # 'should_process' is dynamically set by gclient if it goes over its
156 # recursion limit and controls gclient's behavior so it does not misbehave.
157 self._managed = managed
158 self._should_process = should_process
159
160 # These are only set in .gclient and not in DEPS files.
161 self._custom_vars = custom_vars or {}
162 self._custom_deps = custom_deps or {}
163
maruel@chromium.org064186c2011-09-27 23:53:33 +0000164 # Post process the url to remove trailing slashes.
165 if isinstance(self._url, basestring):
166 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
167 # it to proto://host/path@rev.
168 if self._url.count('@') > 1:
169 raise gclient_utils.Error('Invalid url "%s"' % self._url)
170 self._url = self._url.replace('/@', '@')
171 elif not isinstance(self._url,
172 (self.FromImpl, self.FileImpl, None.__class__)):
173 raise gclient_utils.Error(
174 ('dependency url must be either a string, None, '
175 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000176 if '/' in self._deps_file or '\\' in self._deps_file:
177 raise gclient_utils.Error('deps_file name must not be a path, just a '
178 'filename. %s' % self._deps_file)
179
180 @property
181 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000182 return self._deps_file
183
184 @property
185 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000186 return self._managed
187
188 @property
189 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000190 return self._parent
191
192 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000193 def root(self):
194 """Returns the root node, a GClient object."""
195 if not self.parent:
196 # This line is to signal pylint that it could be a GClient instance.
197 return self or GClient(None, None)
198 return self.parent.root
199
200 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000201 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202 return self._safesync_url
203
204 @property
205 def should_process(self):
206 """True if this dependency should be processed, i.e. checked out."""
207 return self._should_process
208
209 @property
210 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211 return self._custom_vars.copy()
212
213 @property
214 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000215 return self._custom_deps.copy()
216
maruel@chromium.org064186c2011-09-27 23:53:33 +0000217 @property
218 def url(self):
219 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000221 @property
222 def recursion_limit(self):
223 """Returns > 0 if this dependency is not too recursed to be processed."""
224 return max(self.parent.recursion_limit - 1, 0)
225
226 def get_custom_deps(self, name, url):
227 """Returns a custom deps if applicable."""
228 if self.parent:
229 url = self.parent.get_custom_deps(name, url)
230 # None is a valid return value to disable a dependency.
231 return self.custom_deps.get(name, url)
232
maruel@chromium.org064186c2011-09-27 23:53:33 +0000233
234class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000235 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000236
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000237 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000238 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000239 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000241 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000243
244 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000245 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000246
247 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000249 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000250 # A cache of the files affected by the current operation, necessary for
251 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000252 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000253 # If it is not set to True, the dependency wasn't processed for its child
254 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000255 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000256 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000258 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000260
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000261 if not self.name and self.parent:
262 raise gclient_utils.Error('Dependency without name')
263
maruel@chromium.org470b5432011-10-11 18:18:19 +0000264 @property
265 def requirements(self):
266 """Calculate the list of requirements."""
267 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000268 # self.parent is implicitly a requirement. This will be recursive by
269 # definition.
270 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000271 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000272
273 # For a tree with at least 2 levels*, the leaf node needs to depend
274 # on the level higher up in an orderly way.
275 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
276 # thus unsorted, while the .gclient format is a list thus sorted.
277 #
278 # * _recursion_limit is hard coded 2 and there is no hope to change this
279 # value.
280 #
281 # Interestingly enough, the following condition only works in the case we
282 # want: self is a 2nd level node. 3nd level node wouldn't need this since
283 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000284 if self.parent and self.parent.parent and not self.parent.parent.parent:
285 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000286
287 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000288 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000289
maruel@chromium.org470b5432011-10-11 18:18:19 +0000290 if self.name:
291 requirements |= set(
292 obj.name for obj in self.root.subtree(False)
293 if (obj is not self
294 and obj.name and
295 self.name.startswith(posixpath.join(obj.name, ''))))
296 requirements = tuple(sorted(requirements))
297 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
298 return requirements
299
300 def verify_validity(self):
301 """Verifies that this Dependency is fine to add as a child of another one.
302
303 Returns True if this entry should be added, False if it is a duplicate of
304 another entry.
305 """
306 logging.info('Dependency(%s).verify_validity()' % self.name)
307 if self.name in [s.name for s in self.parent.dependencies]:
308 raise gclient_utils.Error(
309 'The same name "%s" appears multiple times in the deps section' %
310 self.name)
311 if not self.should_process:
312 # Return early, no need to set requirements.
313 return True
314
315 # This require a full tree traversal with locks.
316 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
317 for sibling in siblings:
318 if self.url != sibling.url:
319 raise gclient_utils.Error(
320 'Dependency %s specified more than once:\n %s\nvs\n %s' %
321 (self.name, sibling.hierarchy(), self.hierarchy()))
322 # In theory we could keep it as a shadow of the other one. In
323 # practice, simply ignore it.
324 logging.warn('Won\'t process duplicate dependency %s' % sibling)
325 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000326 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000327
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000328 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000329 """Resolves the parsed url from url.
330
331 Manages From() keyword accordingly. Do not touch self.parsed_url nor
332 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000333 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000334 parsed_url = self.get_custom_deps(self.name, url)
335 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000336 logging.info(
337 'Dependency(%s).LateOverride(%s) -> %s' %
338 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000339 return parsed_url
340
341 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000342 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000343 ref = [
344 dep for dep in self.root.subtree(True) if url.module_name == dep.name
345 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000346 if not ref:
347 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
348 url.module_name, ref))
349 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000350 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000351 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000352 found_deps = [d for d in ref.dependencies if d.name == sub_target]
353 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000354 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000355 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
356 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000357 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000358
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000359 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000360 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000361 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000362 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000363 'Dependency(%s).LateOverride(%s) -> %s (From)' %
364 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000365 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000366
367 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000368 parsed_url = urlparse.urlparse(url)
369 if not parsed_url[0]:
370 # A relative url. Fetch the real base.
371 path = parsed_url[2]
372 if not path.startswith('/'):
373 raise gclient_utils.Error(
374 'relative DEPS entry \'%s\' must begin with a slash' % url)
375 # Create a scm just to query the full url.
376 parent_url = self.parent.parsed_url
377 if isinstance(parent_url, self.FileImpl):
378 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000379 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000380 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000381 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000382 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000383 logging.info(
384 'Dependency(%s).LateOverride(%s) -> %s' %
385 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000386 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000387
388 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000389 logging.info(
390 'Dependency(%s).LateOverride(%s) -> %s (File)' %
391 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000392 return url
393
394 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000395 logging.info(
396 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000397 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000398
399 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000400
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000401 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000402 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000403 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000404 assert not self.dependencies
405 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000406 local_scope = {}
407 var = self.VarImpl(self.custom_vars, local_scope)
408 global_scope = {
409 'File': self.FileImpl,
410 'From': self.FromImpl,
411 'Var': var.Lookup,
412 'deps_os': {},
413 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000414 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000415 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000416 logging.info(
417 'ParseDepsFile(%s): No %s file found at %s' % (
418 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000419 else:
420 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000421 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000422 # Eval the content.
423 try:
424 exec(deps_content, global_scope, local_scope)
425 except SyntaxError, e:
426 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000427 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000428 # load os specific dependencies if defined. these dependencies may
429 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000430 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000431 enforced_os = self.root.enforced_os
432 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000433 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000434 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000435 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000436 # platform, so we collect the broadest set of dependencies
437 # available. We may end up with the wrong revision of something for
438 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000439 deps.update([x for x in os_deps.items() if not x[0] in deps])
440 else:
441 deps.update(os_deps)
442
maruel@chromium.org271375b2010-06-23 19:17:38 +0000443 # If a line is in custom_deps, but not in the solution, we want to append
444 # this line to the solution.
445 for d in self.custom_deps:
446 if d not in deps:
447 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000448
449 # If use_relative_paths is set in the DEPS file, regenerate
450 # the dictionary using paths relative to the directory containing
451 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000452 use_relative_paths = local_scope.get('use_relative_paths', False)
453 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000454 rel_deps = {}
455 for d, url in deps.items():
456 # normpath is required to allow DEPS to use .. in their
457 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000458 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
459 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000460
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000461 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000462 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000463 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000464 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000465 deps_to_add.append(Dependency(
466 self, name, url, None, None, None, None,
467 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000468 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000469 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
470 logging.info('ParseDepsFile(%s) done' % self.name)
471
472 def add_dependencies_and_close(self, deps_to_add, hooks):
473 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000474 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000475 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000476 self.add_dependency(dep)
477 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000478
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000479 @staticmethod
480 def maybeGetParentRevision(
481 command, options, parsed_url, parent_name, revision_overrides):
482 """If we are performing an update and --transitive is set, set the
483 revision to the parent's revision. If we have an explicit revision
484 do nothing."""
485 if command == 'update' and options.transitive and not options.revision:
486 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
487 if not revision:
488 options.revision = revision_overrides.get(parent_name)
489 if options.verbose and options.revision:
490 print("Using parent's revision date: %s" % options.revision)
491 # If the parent has a revision override, then it must have been
492 # converted to date format.
493 assert (not options.revision or
494 gclient_utils.IsDateRevision(options.revision))
495
496 @staticmethod
497 def maybeConvertToDateRevision(
498 command, options, name, scm, revision_overrides):
499 """If we are performing an update and --transitive is set, convert the
500 revision to a date-revision (if necessary). Instead of having
501 -r 101 replace the revision with the time stamp of 101 (e.g.
502 "{2011-18-04}").
503 This way dependencies are upgraded to the revision they had at the
504 check-in of revision 101."""
505 if (command == 'update' and
506 options.transitive and
507 options.revision and
508 not gclient_utils.IsDateRevision(options.revision)):
509 revision_date = scm.GetRevisionDate(options.revision)
510 revision = gclient_utils.MakeDateRevision(revision_date)
511 if options.verbose:
512 print("Updating revision override from %s to %s." %
513 (options.revision, revision))
514 revision_overrides[name] = revision
515
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000516 # Arguments number differs from overridden method
517 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000518 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000519 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000520 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000521 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000522 if not self.should_process:
523 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000524 # When running runhooks, there's no need to consult the SCM.
525 # All known hooks are expected to run unconditionally regardless of working
526 # copy state, so skip the SCM status check.
527 run_scm = command not in ('runhooks', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000528 parsed_url = self.LateOverride(self.url)
529 file_list = []
530 if run_scm and parsed_url:
531 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000532 # Special support for single-file checkout.
533 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000534 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
535 # pylint: disable=E1103
536 options.revision = parsed_url.GetRevision()
537 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000538 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000539 self.name)
540 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000541 args + [parsed_url.GetFilename()],
542 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000543 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000544 # Create a shallow copy to mutate revision.
545 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000546 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000547 self.maybeGetParentRevision(
548 command, options, parsed_url, self.parent.name, revision_overrides)
549 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
550 scm.RunCommand(command, options, args, file_list)
551 self.maybeConvertToDateRevision(
552 command, options, self.name, scm, revision_overrides)
553 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000554
555 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
556 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000557 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000558 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000559 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000560 continue
561 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000562 [self.root.root_dir.lower(), file_list[i].lower()])
563 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000564 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000565 while file_list[i].startswith(('\\', '/')):
566 file_list[i] = file_list[i][1:]
567
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000568 # Always parse the DEPS file.
569 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000570
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000571 self._run_is_done(file_list, parsed_url)
572
573 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000574 # Parse the dependencies of this dependency.
575 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000576 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000577
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000578 @gclient_utils.lockedmethod
579 def _run_is_done(self, file_list, parsed_url):
580 # Both these are kept for hooks that are run as a separate tree traversal.
581 self._file_list = file_list
582 self._parsed_url = parsed_url
583 self._processed = True
584
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000585 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000586 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000587 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000588 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000589 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000590 # Don't run the hook when it is above recursion_limit.
591 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000592 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000593 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000594 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000595 # TODO(maruel): If the user is using git or git-svn, then we don't know
596 # what files have changed so we always run all hooks. It'd be nice to fix
597 # that.
598 if (options.force or
599 isinstance(self.parsed_url, self.FileImpl) or
600 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000601 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000602 for hook_dict in self.deps_hooks:
603 self._RunHookAction(hook_dict, [])
604 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000605 # Run hooks on the basis of whether the files from the gclient operation
606 # match each hook's pattern.
607 for hook_dict in self.deps_hooks:
608 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000609 matching_file_list = [
610 f for f in self.file_list_and_children if pattern.search(f)
611 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000612 if matching_file_list:
613 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000614 for s in self.dependencies:
615 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000616
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000617 def _RunHookAction(self, hook_dict, matching_file_list):
618 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000619 # A single DEPS file can specify multiple hooks so this function can be
620 # called multiple times on a single Dependency.
621 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000622 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000623 logging.debug(hook_dict)
624 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000625 command = hook_dict['action'][:]
626 if command[0] == 'python':
627 # If the hook specified "python" as the first item, the action is a
628 # Python script. Run it by starting a new copy of the same
629 # interpreter.
630 command[0] = sys.executable
631
632 if '$matching_files' in command:
633 splice_index = command.index('$matching_files')
634 command[splice_index:splice_index + 1] = matching_file_list
635
maruel@chromium.org17d01792010-09-01 18:07:10 +0000636 try:
637 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000638 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000639 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000640 # Use a discrete exit status code of 2 to indicate that a hook action
641 # failed. Users of this script may wish to treat hook action failures
642 # differently from VC failures.
643 print >> sys.stderr, 'Error: %s' % str(e)
644 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000645
maruel@chromium.org0d812442010-08-10 12:41:08 +0000646 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000647 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000648 dependencies = self.dependencies
649 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000650 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000651 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000652 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000653 for i in d.subtree(include_all):
654 yield i
655
656 def depth_first_tree(self):
657 """Depth-first recursion including the root node."""
658 yield self
659 for i in self.dependencies:
660 for j in i.depth_first_tree():
661 if j.should_process:
662 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000663
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000664 @gclient_utils.lockedmethod
665 def add_dependency(self, new_dep):
666 self._dependencies.append(new_dep)
667
668 @gclient_utils.lockedmethod
669 def _mark_as_parsed(self, new_hooks):
670 self._deps_hooks.extend(new_hooks)
671 self._deps_parsed = True
672
maruel@chromium.org68988972011-09-20 14:11:42 +0000673 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000674 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000675 def dependencies(self):
676 return tuple(self._dependencies)
677
678 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000679 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000680 def deps_hooks(self):
681 return tuple(self._deps_hooks)
682
683 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000684 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000685 def parsed_url(self):
686 return self._parsed_url
687
688 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000689 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000690 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000691 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000692 return self._deps_parsed
693
694 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000695 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000696 def processed(self):
697 return self._processed
698
699 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000700 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000701 def hooks_ran(self):
702 return self._hooks_ran
703
704 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000705 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000706 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000707 return tuple(self._file_list)
708
709 @property
710 def file_list_and_children(self):
711 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000712 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000713 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000714 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000715
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000716 def __str__(self):
717 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000718 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000719 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000720 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000721 # First try the native property if it exists.
722 if hasattr(self, '_' + i):
723 value = getattr(self, '_' + i, False)
724 else:
725 value = getattr(self, i, False)
726 if value:
727 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000728
729 for d in self.dependencies:
730 out.extend([' ' + x for x in str(d).splitlines()])
731 out.append('')
732 return '\n'.join(out)
733
734 def __repr__(self):
735 return '%s: %s' % (self.name, self.url)
736
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000737 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000738 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000739 out = '%s(%s)' % (self.name, self.url)
740 i = self.parent
741 while i and i.name:
742 out = '%s(%s) -> %s' % (i.name, i.url, out)
743 i = i.parent
744 return out
745
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000746
747class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000748 """Object that represent a gclient checkout. A tree of Dependency(), one per
749 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000750
751 DEPS_OS_CHOICES = {
752 "win32": "win",
753 "win": "win",
754 "cygwin": "win",
755 "darwin": "mac",
756 "mac": "mac",
757 "unix": "unix",
758 "linux": "unix",
759 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000760 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000761 }
762
763 DEFAULT_CLIENT_FILE_TEXT = ("""\
764solutions = [
765 { "name" : "%(solution_name)s",
766 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000767 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000768 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000769 "custom_deps" : {
770 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000771 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000772 },
773]
774""")
775
776 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
777 { "name" : "%(solution_name)s",
778 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000779 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000780 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000781 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000782%(solution_deps)s },
783 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000784 },
785""")
786
787 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
788# Snapshot generated with gclient revinfo --snapshot
789solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000790%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000791""")
792
793 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000794 # Do not change previous behavior. Only solution level and immediate DEPS
795 # are processed.
796 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000797 Dependency.__init__(self, None, None, None, None, True, None, None,
798 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000799 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000800 if options.deps_os:
801 enforced_os = options.deps_os.split(',')
802 else:
803 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
804 if 'all' in enforced_os:
805 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000806 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000807 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000808 self.config_content = None
809
810 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000811 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000812 config_dict = {}
813 self.config_content = content
814 try:
815 exec(content, config_dict)
816 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000817 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000818
819 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000820 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000821 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000822 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000823 self, s['name'], s['url'],
824 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000825 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000826 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000827 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000828 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000829 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000830 except KeyError:
831 raise gclient_utils.Error('Invalid .gclient file. Solution is '
832 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000833 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
834 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000835
836 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000837 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000838 self._options.config_filename),
839 self.config_content)
840
841 @staticmethod
842 def LoadCurrentConfig(options):
843 """Searches for and loads a .gclient file relative to the current working
844 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000845 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000846 if not path:
847 return None
848 client = GClient(path, options)
849 client.SetConfig(gclient_utils.FileRead(
850 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000851
852 if (options.revisions and
853 len(client.dependencies) > 1 and
854 any('@' not in r for r in options.revisions)):
855 print >> sys.stderr, (
856 'You must specify the full solution name like --revision %s@%s\n'
857 'when you have multiple solutions setup in your .gclient file.\n'
858 'Other solutions present are: %s.') % (
859 client.dependencies[0].name,
860 options.revisions[0],
861 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000862 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000863
nsylvain@google.comefc80932011-05-31 21:27:56 +0000864 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000865 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000866 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
867 'solution_name': solution_name,
868 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000869 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000870 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000871 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000872 })
873
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000874 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000875 """Creates a .gclient_entries file to record the list of unique checkouts.
876
877 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000878 """
879 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
880 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000881 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000882 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000883 # Skip over File() dependencies as we can't version them.
884 if not isinstance(entry.parsed_url, self.FileImpl):
885 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
886 pprint.pformat(entry.parsed_url))
887 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000888 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000889 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000890 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000891
892 def _ReadEntries(self):
893 """Read the .gclient_entries file for the given client.
894
895 Returns:
896 A sequence of solution names, which will be empty if there is the
897 entries file hasn't been created yet.
898 """
899 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000900 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000901 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000902 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000903 try:
904 exec(gclient_utils.FileRead(filename), scope)
905 except SyntaxError, e:
906 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000907 return scope['entries']
908
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000909 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000910 """Checks for revision overrides."""
911 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000912 if self._options.head:
913 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000914 # Do not check safesync_url if one or more --revision flag is specified.
915 if not self._options.revisions:
916 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000917 if not s.managed:
918 self._options.revisions.append('%s@unmanaged' % s.name)
919 elif s.safesync_url:
920 handle = urllib.urlopen(s.safesync_url)
921 rev = handle.read().strip()
922 handle.close()
923 if len(rev):
924 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000925 if not self._options.revisions:
926 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000927 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000928 index = 0
929 for revision in self._options.revisions:
930 if not '@' in revision:
931 # Support for --revision 123
932 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000933 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000934 if not sol in solutions_names:
935 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
936 print >> sys.stderr, ('Please fix your script, having invalid '
937 '--revision flags will soon considered an error.')
938 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000939 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000940 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000941 return revision_overrides
942
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000943 def RunOnDeps(self, command, args):
944 """Runs a command on each dependency in a client and its dependencies.
945
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000946 Args:
947 command: The command to use (e.g., 'status' or 'diff')
948 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000950 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000951 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000952 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000953 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000954 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000955 if (command in ('update', 'revert') and sys.stdout.isatty() and not
956 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000957 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000958 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000959 for s in self.dependencies:
960 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000961 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000962
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000963 # Once all the dependencies have been processed, it's now safe to run the
964 # hooks.
965 if not self._options.nohooks:
966 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000967
968 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000969 # Notify the user if there is an orphaned entry in their working copy.
970 # Only delete the directory if there are no changes in it, and
971 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000972 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000973 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000974 if not prev_url:
975 # entry must have been overridden via .gclient custom_deps
976 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000977 # Fix path separator on Windows.
978 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000979 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000980 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000981 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000982 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000983 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000984 scm.status(self._options, [], file_list)
985 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000986 if (not self._options.delete_unversioned_trees or
987 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000988 # There are modified files in this entry. Keep warning until
989 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000990 print(('\nWARNING: \'%s\' is no longer part of this client. '
991 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000992 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000993 else:
994 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000995 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000996 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000997 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000998 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000999 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001000 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001001
1002 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001003 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001004 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001005 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001006 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001007 for s in self.dependencies:
1008 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001009 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001010
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001011 def GetURLAndRev(dep):
1012 """Returns the revision-qualified SCM url for a Dependency."""
1013 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001014 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001015 if isinstance(dep.parsed_url, self.FileImpl):
1016 original_url = dep.parsed_url.file_location
1017 else:
1018 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001019 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001020 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001021 if not os.path.isdir(scm.checkout_path):
1022 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001023 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001024
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001025 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001026 new_gclient = ''
1027 # First level at .gclient
1028 for d in self.dependencies:
1029 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001030 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001031 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001032 for d in dep.dependencies:
1033 entries[d.name] = GetURLAndRev(d)
1034 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001035 GrabDeps(d)
1036 custom_deps = []
1037 for k in sorted(entries.keys()):
1038 if entries[k]:
1039 # Quotes aren't escaped...
1040 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1041 else:
1042 custom_deps.append(' \"%s\": None,\n' % k)
1043 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1044 'solution_name': d.name,
1045 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001046 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001047 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001048 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001049 'solution_deps': ''.join(custom_deps),
1050 }
1051 # Print the snapshot configuration file
1052 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001053 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001054 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001055 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001056 if self._options.actual:
1057 entries[d.name] = GetURLAndRev(d)
1058 else:
1059 entries[d.name] = d.parsed_url
1060 keys = sorted(entries.keys())
1061 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001062 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001063 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001065 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001066 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001067 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001068
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001069 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001070 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001071 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001072 return self._root_dir
1073
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001074 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001075 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001076 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001077 return self._enforced_os
1078
maruel@chromium.org68988972011-09-20 14:11:42 +00001079 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001080 def recursion_limit(self):
1081 """How recursive can each dependencies in DEPS file can load DEPS file."""
1082 return self._recursion_limit
1083
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001084
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001085#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001086
1087
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001088def CMDcleanup(parser, args):
1089 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001090
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001091Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001092"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001093 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1094 help='override deps for the specified (comma-separated) '
1095 'platform(s); \'all\' will process all deps_os '
1096 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001097 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001098 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001099 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001100 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001101 if options.verbose:
1102 # Print out the .gclient file. This is longer than if we just printed the
1103 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001104 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001105 return client.RunOnDeps('cleanup', args)
1106
1107
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001108@attr('usage', '[command] [args ...]')
1109def CMDrecurse(parser, args):
1110 """Operates on all the entries.
1111
1112 Runs a shell command on all entries.
1113 """
1114 # Stop parsing at the first non-arg so that these go through to the command
1115 parser.disable_interspersed_args()
1116 parser.add_option('-s', '--scm', action='append', default=[],
1117 help='choose scm types to operate upon')
maruel@chromium.orgf2ff9482011-10-19 19:45:54 +00001118 parser.remove_option('--jobs')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001119 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001120 if not args:
1121 print >> sys.stderr, 'Need to supply a command!'
1122 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001123 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1124 if not root_and_entries:
1125 print >> sys.stderr, (
1126 'You need to run gclient sync at least once to use \'recurse\'.\n'
1127 'This is because .gclient_entries needs to exist and be up to date.')
1128 return 1
1129 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001130 scm_set = set()
1131 for scm in options.scm:
1132 scm_set.update(scm.split(','))
1133
1134 # Pass in the SCM type as an env variable
1135 env = os.environ.copy()
1136
1137 for path, url in entries.iteritems():
1138 scm = gclient_scm.GetScmName(url)
1139 if scm_set and scm not in scm_set:
1140 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001141 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001142 if scm:
1143 env['GCLIENT_SCM'] = scm
1144 if url:
1145 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001146 if os.path.isdir(cwd):
1147 subprocess2.call(args, cwd=cwd, env=env)
1148 else:
1149 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001150 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001151
1152
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001153@attr('usage', '[url] [safesync url]')
1154def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001155 """Create a .gclient file in the current directory.
1156
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001157This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001158top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001159modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001160provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001161URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001162"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001163 parser.add_option('--spec',
1164 help='create a gclient file containing the provided '
1165 'string. Due to Cygwin/Python brokenness, it '
1166 'probably can\'t contain any newlines.')
1167 parser.add_option('--name',
1168 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001169 parser.add_option('--deps-file', default='DEPS',
1170 help='overrides the default name for the DEPS file for the'
1171 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001172 parser.add_option('--unmanaged', action='store_true', default=False,
1173 help='overrides the default behavior to make it possible '
1174 'to have the main solution untouched by gclient '
1175 '(gclient will check out unmanaged dependencies but '
1176 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001177 parser.add_option('--git-deps', action='store_true',
1178 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001179 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001180 if ((options.spec and args) or len(args) > 2 or
1181 (not options.spec and not args)):
1182 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1183
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001184 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001185 if options.spec:
1186 client.SetConfig(options.spec)
1187 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001188 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001189 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001190 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001191 if name.endswith('.git'):
1192 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001193 else:
1194 # specify an alternate relpath for the given URL.
1195 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001196 deps_file = options.deps_file
1197 if options.git_deps:
1198 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001199 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001200 if len(args) > 1:
1201 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001202 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1203 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001204 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001205 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001206
1207
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001208@attr('epilog', """Example:
1209 gclient pack > patch.txt
1210 generate simple patch for configured client and dependences
1211""")
1212def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001213 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001214
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001215Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001216dependencies, and performs minimal postprocessing of the output. The
1217resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001218checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001219"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001220 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1221 help='override deps for the specified (comma-separated) '
1222 'platform(s); \'all\' will process all deps_os '
1223 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001224 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001225 client = GClient.LoadCurrentConfig(options)
1226 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001227 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001228 if options.verbose:
1229 # Print out the .gclient file. This is longer than if we just printed the
1230 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001231 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001232 return client.RunOnDeps('pack', args)
1233
1234
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001235def CMDstatus(parser, args):
1236 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001237 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1238 help='override deps for the specified (comma-separated) '
1239 'platform(s); \'all\' will process all deps_os '
1240 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001241 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001242 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001243 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001244 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001245 if options.verbose:
1246 # Print out the .gclient file. This is longer than if we just printed the
1247 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001248 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001249 return client.RunOnDeps('status', args)
1250
1251
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001252@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001253 gclient sync
1254 update files from SCM according to current configuration,
1255 *for modules which have changed since last update or sync*
1256 gclient sync --force
1257 update files from SCM according to current configuration, for
1258 all modules (useful for recovering files deleted from local copy)
1259 gclient sync --revision src@31000
1260 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001261""")
1262def CMDsync(parser, args):
1263 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001264 parser.add_option('-f', '--force', action='store_true',
1265 help='force update even for unchanged modules')
1266 parser.add_option('-n', '--nohooks', action='store_true',
1267 help='don\'t run hooks after the update is complete')
1268 parser.add_option('-r', '--revision', action='append',
1269 dest='revisions', metavar='REV', default=[],
1270 help='Enforces revision/hash for the solutions with the '
1271 'format src@rev. The src@ part is optional and can be '
1272 'skipped. -r can be used multiple times when .gclient '
1273 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001274 'if the src@ part is skipped. Note that specifying '
1275 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001276 parser.add_option('-t', '--transitive', action='store_true',
1277 help='When a revision is specified (in the DEPS file or '
1278 'with the command-line flag), transitively update '
1279 'the dependencies to the date of the given revision. '
1280 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001281 parser.add_option('-H', '--head', action='store_true',
1282 help='skips any safesync_urls specified in '
1283 'configured solutions and sync to head instead')
1284 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001285 help='delete any dependency that have been removed from '
1286 'last sync as long as there is no local modification. '
1287 'Coupled with --force, it will remove them even with '
1288 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001289 parser.add_option('-R', '--reset', action='store_true',
1290 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001291 parser.add_option('-M', '--merge', action='store_true',
1292 help='merge upstream changes instead of trying to '
1293 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001294 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1295 help='override deps for the specified (comma-separated) '
1296 'platform(s); \'all\' will process all deps_os '
1297 'references')
1298 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1299 help='Skip svn up whenever possible by requesting '
1300 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001301 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001302 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001303
1304 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001305 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001306
maruel@chromium.org307d1792010-05-31 20:03:13 +00001307 if options.revisions and options.head:
1308 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001309 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001310
1311 if options.verbose:
1312 # Print out the .gclient file. This is longer than if we just printed the
1313 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001314 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001315 return client.RunOnDeps('update', args)
1316
1317
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001318def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001319 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001320 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001321
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001322def CMDdiff(parser, args):
1323 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001324 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1325 help='override deps for the specified (comma-separated) '
1326 'platform(s); \'all\' will process all deps_os '
1327 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001328 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001329 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001330 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001331 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001332 if options.verbose:
1333 # Print out the .gclient file. This is longer than if we just printed the
1334 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001335 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001336 return client.RunOnDeps('diff', args)
1337
1338
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001339def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001340 """Revert all modifications in every dependencies.
1341
1342 That's the nuclear option to get back to a 'clean' state. It removes anything
1343 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001344 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1345 help='override deps for the specified (comma-separated) '
1346 'platform(s); \'all\' will process all deps_os '
1347 'references')
1348 parser.add_option('-n', '--nohooks', action='store_true',
1349 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001350 (options, args) = parser.parse_args(args)
1351 # --force is implied.
1352 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001353 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001354 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001355 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001356 return client.RunOnDeps('revert', args)
1357
1358
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001359def CMDrunhooks(parser, args):
1360 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001361 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1362 help='override deps for the specified (comma-separated) '
1363 'platform(s); \'all\' will process all deps_os '
1364 'references')
1365 parser.add_option('-f', '--force', action='store_true', default=True,
1366 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001367 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001368 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001369 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001370 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001371 if options.verbose:
1372 # Print out the .gclient file. This is longer than if we just printed the
1373 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001374 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001375 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001376 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001377 return client.RunOnDeps('runhooks', args)
1378
1379
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001380def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001381 """Output revision info mapping for the client and its dependencies.
1382
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001383 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001384 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001385 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1386 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001387 commit can change.
1388 """
1389 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1390 help='override deps for the specified (comma-separated) '
1391 'platform(s); \'all\' will process all deps_os '
1392 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001393 parser.add_option('-a', '--actual', action='store_true',
1394 help='gets the actual checked out revisions instead of the '
1395 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001396 parser.add_option('-s', '--snapshot', action='store_true',
1397 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001398 'version of all repositories to reproduce the tree, '
1399 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001400 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001401 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001402 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001403 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001404 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001405 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001406
1407
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001408def Command(name):
1409 return getattr(sys.modules[__name__], 'CMD' + name, None)
1410
1411
1412def CMDhelp(parser, args):
1413 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001414 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001415 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001416 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001417 parser.print_help()
1418 return 0
1419
1420
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001421def GenUsage(parser, command):
1422 """Modify an OptParse object with the function's documentation."""
1423 obj = Command(command)
1424 if command == 'help':
1425 command = '<command>'
1426 # OptParser.description prefer nicely non-formatted strings.
1427 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1428 usage = getattr(obj, 'usage', '')
1429 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1430 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001431
1432
maruel@chromium.org0895b752011-08-26 20:40:33 +00001433def Parser():
1434 """Returns the default parser."""
1435 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org41071612011-10-19 19:58:08 +00001436 # cygwin has issues with parallel sync
1437 jobs = 1 if sys.platform == 'cygwin' else 8
1438 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001439 help='Specify how many SCM commands can run in parallel; '
1440 'default=%default')
1441 parser.add_option('-v', '--verbose', action='count', default=0,
1442 help='Produces additional output for diagnostics. Can be '
1443 'used up to three times for more logging info.')
1444 parser.add_option('--gclientfile', dest='config_filename',
1445 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1446 help='Specify an alternate %default file')
1447 # Integrate standard options processing.
1448 old_parser = parser.parse_args
1449 def Parse(args):
1450 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001451 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1452 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001453 logging.basicConfig(level=level,
1454 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1455 options.entries_filename = options.config_filename + '_entries'
1456 if options.jobs < 1:
1457 parser.error('--jobs must be 1 or higher')
1458
1459 # These hacks need to die.
1460 if not hasattr(options, 'revisions'):
1461 # GClient.RunOnDeps expects it even if not applicable.
1462 options.revisions = []
1463 if not hasattr(options, 'head'):
1464 options.head = None
1465 if not hasattr(options, 'nohooks'):
1466 options.nohooks = True
1467 if not hasattr(options, 'deps_os'):
1468 options.deps_os = None
1469 if not hasattr(options, 'manually_grab_svn_rev'):
1470 options.manually_grab_svn_rev = None
1471 if not hasattr(options, 'force'):
1472 options.force = None
1473 return (options, args)
1474 parser.parse_args = Parse
1475 # We don't want wordwrapping in epilog (usually examples)
1476 parser.format_epilog = lambda _: parser.epilog or ''
1477 return parser
1478
1479
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001480def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001481 """Doesn't parse the arguments here, just find the right subcommand to
1482 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001483 if sys.hexversion < 0x02050000:
1484 print >> sys.stderr, (
1485 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001486 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001487 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1488 # operations. Python as a strong tendency to buffer sys.stdout.
1489 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001490 # Make stdout annotated with the thread ids.
1491 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001492 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001493 # Unused variable 'usage'
1494 # pylint: disable=W0612
maruel@chromium.orgd8aba2c2011-10-21 12:57:15 +00001495 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1496 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1497 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001498 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001499 if argv:
1500 command = Command(argv[0])
1501 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001502 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001503 GenUsage(parser, argv[0])
1504 return command(parser, argv[1:])
1505 # Not a known command. Default to help.
1506 GenUsage(parser, 'help')
1507 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001508 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001509 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001510 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001511
1512
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001513if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001514 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001515 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001516
1517# vim: ts=2:sw=2:tw=80:et: