blob: 243fb59a668de5c953896b6ed0645d9212be0bc0 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 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@chromium.orgda78c6f2011-10-23 00:13:58 +000072from third_party import colorama
73# Import shortcut.
74from third_party.colorama import Fore
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000075
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000077def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000078 """Sets an attribute on a function."""
79 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000080 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000081 return fn
82 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000083
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085## GClient implementation.
86
87
maruel@chromium.org116704f2010-06-11 17:34:38 +000088class GClientKeywords(object):
89 class FromImpl(object):
90 """Used to implement the From() syntax."""
91
92 def __init__(self, module_name, sub_target_name=None):
93 """module_name is the dep module we want to include from. It can also be
94 the name of a subdirectory to include from.
95
96 sub_target_name is an optional parameter if the module name in the other
97 DEPS file is different. E.g., you might want to map src/net to net."""
98 self.module_name = module_name
99 self.sub_target_name = sub_target_name
100
101 def __str__(self):
102 return 'From(%s, %s)' % (repr(self.module_name),
103 repr(self.sub_target_name))
104
maruel@chromium.org116704f2010-06-11 17:34:38 +0000105 class FileImpl(object):
106 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000107 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000108
109 def __init__(self, file_location):
110 self.file_location = file_location
111
112 def __str__(self):
113 return 'File("%s")' % self.file_location
114
115 def GetPath(self):
116 return os.path.split(self.file_location)[0]
117
118 def GetFilename(self):
119 rev_tokens = self.file_location.split('@')
120 return os.path.split(rev_tokens[0])[1]
121
122 def GetRevision(self):
123 rev_tokens = self.file_location.split('@')
124 if len(rev_tokens) > 1:
125 return rev_tokens[1]
126 return None
127
128 class VarImpl(object):
129 def __init__(self, custom_vars, local_scope):
130 self._custom_vars = custom_vars
131 self._local_scope = local_scope
132
133 def Lookup(self, var_name):
134 """Implements the Var syntax."""
135 if var_name in self._custom_vars:
136 return self._custom_vars[var_name]
137 elif var_name in self._local_scope.get("vars", {}):
138 return self._local_scope["vars"][var_name]
139 raise gclient_utils.Error("Var is not defined: %s" % var_name)
140
141
maruel@chromium.org064186c2011-09-27 23:53:33 +0000142class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000143 """Immutable configuration settings."""
144 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000145 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000146 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000147 GClientKeywords.__init__(self)
148
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000149 # These are not mutable:
150 self._parent = parent
151 self._safesync_url = safesync_url
152 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000153 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000154 # 'managed' determines whether or not this dependency is synced/updated by
155 # gclient after gclient checks it out initially. The difference between
156 # 'managed' and 'should_process' is that the user specifies 'managed' via
157 # the --unmanaged command-line flag or a .gclient config, where
158 # 'should_process' is dynamically set by gclient if it goes over its
159 # recursion limit and controls gclient's behavior so it does not misbehave.
160 self._managed = managed
161 self._should_process = should_process
162
163 # These are only set in .gclient and not in DEPS files.
164 self._custom_vars = custom_vars or {}
165 self._custom_deps = custom_deps or {}
166
maruel@chromium.org064186c2011-09-27 23:53:33 +0000167 # Post process the url to remove trailing slashes.
168 if isinstance(self._url, basestring):
169 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
170 # it to proto://host/path@rev.
171 if self._url.count('@') > 1:
172 raise gclient_utils.Error('Invalid url "%s"' % self._url)
173 self._url = self._url.replace('/@', '@')
174 elif not isinstance(self._url,
175 (self.FromImpl, self.FileImpl, None.__class__)):
176 raise gclient_utils.Error(
177 ('dependency url must be either a string, None, '
178 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000179 if '/' in self._deps_file or '\\' in self._deps_file:
180 raise gclient_utils.Error('deps_file name must not be a path, just a '
181 'filename. %s' % self._deps_file)
182
183 @property
184 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000185 return self._deps_file
186
187 @property
188 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000189 return self._managed
190
191 @property
192 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000193 return self._parent
194
195 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000196 def root(self):
197 """Returns the root node, a GClient object."""
198 if not self.parent:
199 # This line is to signal pylint that it could be a GClient instance.
200 return self or GClient(None, None)
201 return self.parent.root
202
203 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000204 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000205 return self._safesync_url
206
207 @property
208 def should_process(self):
209 """True if this dependency should be processed, i.e. checked out."""
210 return self._should_process
211
212 @property
213 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 return self._custom_vars.copy()
215
216 @property
217 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218 return self._custom_deps.copy()
219
maruel@chromium.org064186c2011-09-27 23:53:33 +0000220 @property
221 def url(self):
222 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000223
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000224 @property
225 def recursion_limit(self):
226 """Returns > 0 if this dependency is not too recursed to be processed."""
227 return max(self.parent.recursion_limit - 1, 0)
228
229 def get_custom_deps(self, name, url):
230 """Returns a custom deps if applicable."""
231 if self.parent:
232 url = self.parent.get_custom_deps(name, url)
233 # None is a valid return value to disable a dependency.
234 return self.custom_deps.get(name, url)
235
maruel@chromium.org064186c2011-09-27 23:53:33 +0000236
237class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000238 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000239
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000240 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000241 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000242 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000244 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000245 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000246
247 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000249
250 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000251 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000252 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000253 # A cache of the files affected by the current operation, necessary for
254 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000255 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000256 # If it is not set to True, the dependency wasn't processed for its child
257 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000258 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000259 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000260 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000261 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000262 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000263
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000264 if not self.name and self.parent:
265 raise gclient_utils.Error('Dependency without name')
266
maruel@chromium.org470b5432011-10-11 18:18:19 +0000267 @property
268 def requirements(self):
269 """Calculate the list of requirements."""
270 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000271 # self.parent is implicitly a requirement. This will be recursive by
272 # definition.
273 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000274 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000275
276 # For a tree with at least 2 levels*, the leaf node needs to depend
277 # on the level higher up in an orderly way.
278 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
279 # thus unsorted, while the .gclient format is a list thus sorted.
280 #
281 # * _recursion_limit is hard coded 2 and there is no hope to change this
282 # value.
283 #
284 # Interestingly enough, the following condition only works in the case we
285 # want: self is a 2nd level node. 3nd level node wouldn't need this since
286 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000287 if self.parent and self.parent.parent and not self.parent.parent.parent:
288 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000289
290 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000291 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000292
maruel@chromium.org470b5432011-10-11 18:18:19 +0000293 if self.name:
294 requirements |= set(
295 obj.name for obj in self.root.subtree(False)
296 if (obj is not self
297 and obj.name and
298 self.name.startswith(posixpath.join(obj.name, ''))))
299 requirements = tuple(sorted(requirements))
300 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
301 return requirements
302
303 def verify_validity(self):
304 """Verifies that this Dependency is fine to add as a child of another one.
305
306 Returns True if this entry should be added, False if it is a duplicate of
307 another entry.
308 """
309 logging.info('Dependency(%s).verify_validity()' % self.name)
310 if self.name in [s.name for s in self.parent.dependencies]:
311 raise gclient_utils.Error(
312 'The same name "%s" appears multiple times in the deps section' %
313 self.name)
314 if not self.should_process:
315 # Return early, no need to set requirements.
316 return True
317
318 # This require a full tree traversal with locks.
319 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
320 for sibling in siblings:
321 if self.url != sibling.url:
322 raise gclient_utils.Error(
323 'Dependency %s specified more than once:\n %s\nvs\n %s' %
324 (self.name, sibling.hierarchy(), self.hierarchy()))
325 # In theory we could keep it as a shadow of the other one. In
326 # practice, simply ignore it.
327 logging.warn('Won\'t process duplicate dependency %s' % sibling)
328 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000329 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000330
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000331 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000332 """Resolves the parsed url from url.
333
334 Manages From() keyword accordingly. Do not touch self.parsed_url nor
335 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000336 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000337 parsed_url = self.get_custom_deps(self.name, url)
338 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000339 logging.info(
340 'Dependency(%s).LateOverride(%s) -> %s' %
341 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000342 return parsed_url
343
344 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000345 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000346 ref = [
347 dep for dep in self.root.subtree(True) if url.module_name == dep.name
348 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000349 if not ref:
350 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
351 url.module_name, ref))
352 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000353 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000354 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000355 found_deps = [d for d in ref.dependencies if d.name == sub_target]
356 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000357 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000358 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
359 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000360 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000361
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000363 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000364 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000365 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000366 'Dependency(%s).LateOverride(%s) -> %s (From)' %
367 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000368 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000369
370 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000371 parsed_url = urlparse.urlparse(url)
372 if not parsed_url[0]:
373 # A relative url. Fetch the real base.
374 path = parsed_url[2]
375 if not path.startswith('/'):
376 raise gclient_utils.Error(
377 'relative DEPS entry \'%s\' must begin with a slash' % url)
378 # Create a scm just to query the full url.
379 parent_url = self.parent.parsed_url
380 if isinstance(parent_url, self.FileImpl):
381 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000382 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000383 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000384 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000385 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000386 logging.info(
387 'Dependency(%s).LateOverride(%s) -> %s' %
388 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000389 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000390
391 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000392 logging.info(
393 'Dependency(%s).LateOverride(%s) -> %s (File)' %
394 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000395 return url
396
397 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000398 logging.info(
399 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000400 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000401
402 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000403
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000404 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000405 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000406 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000407 assert not self.dependencies
408 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000409 local_scope = {}
410 var = self.VarImpl(self.custom_vars, local_scope)
411 global_scope = {
412 'File': self.FileImpl,
413 'From': self.FromImpl,
414 'Var': var.Lookup,
415 'deps_os': {},
416 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000417 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000418 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000419 logging.info(
420 'ParseDepsFile(%s): No %s file found at %s' % (
421 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000422 else:
423 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000424 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000425 # Eval the content.
426 try:
427 exec(deps_content, global_scope, local_scope)
428 except SyntaxError, e:
429 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000430 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000431 # load os specific dependencies if defined. these dependencies may
432 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000433 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000434 enforced_os = self.root.enforced_os
435 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000436 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000437 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000438 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000439 # platform, so we collect the broadest set of dependencies
440 # available. We may end up with the wrong revision of something for
441 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000442 deps.update([x for x in os_deps.items() if not x[0] in deps])
443 else:
444 deps.update(os_deps)
445
maruel@chromium.org271375b2010-06-23 19:17:38 +0000446 # If a line is in custom_deps, but not in the solution, we want to append
447 # this line to the solution.
448 for d in self.custom_deps:
449 if d not in deps:
450 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000451
452 # If use_relative_paths is set in the DEPS file, regenerate
453 # the dictionary using paths relative to the directory containing
454 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000455 use_relative_paths = local_scope.get('use_relative_paths', False)
456 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000457 rel_deps = {}
458 for d, url in deps.items():
459 # normpath is required to allow DEPS to use .. in their
460 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000461 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
462 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000463
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000464 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000465 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000466 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000467 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000468 deps_to_add.append(Dependency(
469 self, name, url, None, None, None, None,
470 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000471 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000472 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
473 logging.info('ParseDepsFile(%s) done' % self.name)
474
475 def add_dependencies_and_close(self, deps_to_add, hooks):
476 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000477 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000478 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000479 self.add_dependency(dep)
480 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000481
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000482 @staticmethod
483 def maybeGetParentRevision(
484 command, options, parsed_url, parent_name, revision_overrides):
485 """If we are performing an update and --transitive is set, set the
486 revision to the parent's revision. If we have an explicit revision
487 do nothing."""
488 if command == 'update' and options.transitive and not options.revision:
489 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
490 if not revision:
491 options.revision = revision_overrides.get(parent_name)
492 if options.verbose and options.revision:
493 print("Using parent's revision date: %s" % options.revision)
494 # If the parent has a revision override, then it must have been
495 # converted to date format.
496 assert (not options.revision or
497 gclient_utils.IsDateRevision(options.revision))
498
499 @staticmethod
500 def maybeConvertToDateRevision(
501 command, options, name, scm, revision_overrides):
502 """If we are performing an update and --transitive is set, convert the
503 revision to a date-revision (if necessary). Instead of having
504 -r 101 replace the revision with the time stamp of 101 (e.g.
505 "{2011-18-04}").
506 This way dependencies are upgraded to the revision they had at the
507 check-in of revision 101."""
508 if (command == 'update' and
509 options.transitive and
510 options.revision and
511 not gclient_utils.IsDateRevision(options.revision)):
512 revision_date = scm.GetRevisionDate(options.revision)
513 revision = gclient_utils.MakeDateRevision(revision_date)
514 if options.verbose:
515 print("Updating revision override from %s to %s." %
516 (options.revision, revision))
517 revision_overrides[name] = revision
518
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000519 # Arguments number differs from overridden method
520 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000521 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000522 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000523 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000524 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000525 if not self.should_process:
526 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000527 # When running runhooks, there's no need to consult the SCM.
528 # All known hooks are expected to run unconditionally regardless of working
529 # copy state, so skip the SCM status check.
530 run_scm = command not in ('runhooks', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000531 parsed_url = self.LateOverride(self.url)
532 file_list = []
533 if run_scm and parsed_url:
534 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000535 # Special support for single-file checkout.
536 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000537 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
538 # pylint: disable=E1103
539 options.revision = parsed_url.GetRevision()
540 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000541 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000542 self.name)
543 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000544 args + [parsed_url.GetFilename()],
545 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000546 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000547 # Create a shallow copy to mutate revision.
548 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000549 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000550 self.maybeGetParentRevision(
551 command, options, parsed_url, self.parent.name, revision_overrides)
552 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
553 scm.RunCommand(command, options, args, file_list)
554 self.maybeConvertToDateRevision(
555 command, options, self.name, scm, revision_overrides)
556 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000557
558 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
559 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000560 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000561 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000562 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000563 continue
564 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000565 [self.root.root_dir.lower(), file_list[i].lower()])
566 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000567 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000568 while file_list[i].startswith(('\\', '/')):
569 file_list[i] = file_list[i][1:]
570
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000571 # Always parse the DEPS file.
572 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000573
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000574 self._run_is_done(file_list, parsed_url)
575
576 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000577 # Parse the dependencies of this dependency.
578 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000579 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000580
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000581 @gclient_utils.lockedmethod
582 def _run_is_done(self, file_list, parsed_url):
583 # Both these are kept for hooks that are run as a separate tree traversal.
584 self._file_list = file_list
585 self._parsed_url = parsed_url
586 self._processed = True
587
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000588 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000589 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000590 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000591 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000592 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000593 # Don't run the hook when it is above recursion_limit.
594 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000595 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000596 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000597 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000598 # TODO(maruel): If the user is using git or git-svn, then we don't know
599 # what files have changed so we always run all hooks. It'd be nice to fix
600 # that.
601 if (options.force or
602 isinstance(self.parsed_url, self.FileImpl) or
603 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000604 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000605 for hook_dict in self.deps_hooks:
606 self._RunHookAction(hook_dict, [])
607 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000608 # Run hooks on the basis of whether the files from the gclient operation
609 # match each hook's pattern.
610 for hook_dict in self.deps_hooks:
611 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000612 matching_file_list = [
613 f for f in self.file_list_and_children if pattern.search(f)
614 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000615 if matching_file_list:
616 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000617 for s in self.dependencies:
618 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000619
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000620 def _RunHookAction(self, hook_dict, matching_file_list):
621 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000622 # A single DEPS file can specify multiple hooks so this function can be
623 # called multiple times on a single Dependency.
624 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000625 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000626 logging.debug(hook_dict)
627 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000628 command = hook_dict['action'][:]
629 if command[0] == 'python':
630 # If the hook specified "python" as the first item, the action is a
631 # Python script. Run it by starting a new copy of the same
632 # interpreter.
633 command[0] = sys.executable
634
635 if '$matching_files' in command:
636 splice_index = command.index('$matching_files')
637 command[splice_index:splice_index + 1] = matching_file_list
638
maruel@chromium.org17d01792010-09-01 18:07:10 +0000639 try:
640 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000641 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000642 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000643 # Use a discrete exit status code of 2 to indicate that a hook action
644 # failed. Users of this script may wish to treat hook action failures
645 # differently from VC failures.
646 print >> sys.stderr, 'Error: %s' % str(e)
647 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000648
maruel@chromium.org0d812442010-08-10 12:41:08 +0000649 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000650 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000651 dependencies = self.dependencies
652 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000653 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000654 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000655 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000656 for i in d.subtree(include_all):
657 yield i
658
659 def depth_first_tree(self):
660 """Depth-first recursion including the root node."""
661 yield self
662 for i in self.dependencies:
663 for j in i.depth_first_tree():
664 if j.should_process:
665 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000666
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000667 @gclient_utils.lockedmethod
668 def add_dependency(self, new_dep):
669 self._dependencies.append(new_dep)
670
671 @gclient_utils.lockedmethod
672 def _mark_as_parsed(self, new_hooks):
673 self._deps_hooks.extend(new_hooks)
674 self._deps_parsed = True
675
maruel@chromium.org68988972011-09-20 14:11:42 +0000676 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000677 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000678 def dependencies(self):
679 return tuple(self._dependencies)
680
681 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000682 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000683 def deps_hooks(self):
684 return tuple(self._deps_hooks)
685
686 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000687 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000688 def parsed_url(self):
689 return self._parsed_url
690
691 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000692 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000693 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000694 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000695 return self._deps_parsed
696
697 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000698 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000699 def processed(self):
700 return self._processed
701
702 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000703 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000704 def hooks_ran(self):
705 return self._hooks_ran
706
707 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000708 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000709 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000710 return tuple(self._file_list)
711
712 @property
713 def file_list_and_children(self):
714 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000715 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000716 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000717 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000718
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000719 def __str__(self):
720 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000721 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000722 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000723 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000724 # First try the native property if it exists.
725 if hasattr(self, '_' + i):
726 value = getattr(self, '_' + i, False)
727 else:
728 value = getattr(self, i, False)
729 if value:
730 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000731
732 for d in self.dependencies:
733 out.extend([' ' + x for x in str(d).splitlines()])
734 out.append('')
735 return '\n'.join(out)
736
737 def __repr__(self):
738 return '%s: %s' % (self.name, self.url)
739
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000740 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000741 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000742 out = '%s(%s)' % (self.name, self.url)
743 i = self.parent
744 while i and i.name:
745 out = '%s(%s) -> %s' % (i.name, i.url, out)
746 i = i.parent
747 return out
748
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000749
750class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000751 """Object that represent a gclient checkout. A tree of Dependency(), one per
752 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000753
754 DEPS_OS_CHOICES = {
755 "win32": "win",
756 "win": "win",
757 "cygwin": "win",
758 "darwin": "mac",
759 "mac": "mac",
760 "unix": "unix",
761 "linux": "unix",
762 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000763 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000764 }
765
766 DEFAULT_CLIENT_FILE_TEXT = ("""\
767solutions = [
768 { "name" : "%(solution_name)s",
769 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000770 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000771 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000772 "custom_deps" : {
773 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000774 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000775 },
776]
777""")
778
779 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
780 { "name" : "%(solution_name)s",
781 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000782 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000783 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000784 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000785%(solution_deps)s },
786 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000787 },
788""")
789
790 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
791# Snapshot generated with gclient revinfo --snapshot
792solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000793%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000794""")
795
796 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000797 # Do not change previous behavior. Only solution level and immediate DEPS
798 # are processed.
799 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000800 Dependency.__init__(self, None, None, None, None, True, None, None,
801 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000802 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000803 if options.deps_os:
804 enforced_os = options.deps_os.split(',')
805 else:
806 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
807 if 'all' in enforced_os:
808 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000809 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000810 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000811 self.config_content = None
812
813 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000814 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000815 config_dict = {}
816 self.config_content = content
817 try:
818 exec(content, config_dict)
819 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000820 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000821
822 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000823 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000824 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000825 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000826 self, s['name'], s['url'],
827 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000828 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000829 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000830 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000831 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000832 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000833 except KeyError:
834 raise gclient_utils.Error('Invalid .gclient file. Solution is '
835 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000836 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
837 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000838
839 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000840 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000841 self._options.config_filename),
842 self.config_content)
843
844 @staticmethod
845 def LoadCurrentConfig(options):
846 """Searches for and loads a .gclient file relative to the current working
847 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000848 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000849 if not path:
850 return None
851 client = GClient(path, options)
852 client.SetConfig(gclient_utils.FileRead(
853 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000854
855 if (options.revisions and
856 len(client.dependencies) > 1 and
857 any('@' not in r for r in options.revisions)):
858 print >> sys.stderr, (
859 'You must specify the full solution name like --revision %s@%s\n'
860 'when you have multiple solutions setup in your .gclient file.\n'
861 'Other solutions present are: %s.') % (
862 client.dependencies[0].name,
863 options.revisions[0],
864 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000865 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000866
nsylvain@google.comefc80932011-05-31 21:27:56 +0000867 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000868 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000869 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
870 'solution_name': solution_name,
871 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000872 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000873 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000874 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000875 })
876
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000877 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000878 """Creates a .gclient_entries file to record the list of unique checkouts.
879
880 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000881 """
882 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
883 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000884 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000885 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000886 # Skip over File() dependencies as we can't version them.
887 if not isinstance(entry.parsed_url, self.FileImpl):
888 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
889 pprint.pformat(entry.parsed_url))
890 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000891 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000892 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000893 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000894
895 def _ReadEntries(self):
896 """Read the .gclient_entries file for the given client.
897
898 Returns:
899 A sequence of solution names, which will be empty if there is the
900 entries file hasn't been created yet.
901 """
902 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000903 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000904 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000905 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000906 try:
907 exec(gclient_utils.FileRead(filename), scope)
908 except SyntaxError, e:
909 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000910 return scope['entries']
911
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000912 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000913 """Checks for revision overrides."""
914 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000915 if self._options.head:
916 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000917 # Do not check safesync_url if one or more --revision flag is specified.
918 if not self._options.revisions:
919 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000920 if not s.managed:
921 self._options.revisions.append('%s@unmanaged' % s.name)
922 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000923 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000924 if not self._options.revisions:
925 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000926 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000927 index = 0
928 for revision in self._options.revisions:
929 if not '@' in revision:
930 # Support for --revision 123
931 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000932 sol, rev = revision.split('@', 1)
thakis@chromium.org4f474b62012-01-18 01:31:29 +0000933
934 if not sol in solutions_names and sol == 'trunk':
935 print >> sys.stderr, 'Rewriting "trunk" to "src" in --revision'
936 # Fix attempt for http://crbug.com/108515
937 sol = 'src'
938
maruel@chromium.org307d1792010-05-31 20:03:13 +0000939 if not sol in solutions_names:
940 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
941 print >> sys.stderr, ('Please fix your script, having invalid '
942 '--revision flags will soon considered an error.')
943 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000944 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000945 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000946 return revision_overrides
947
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000948 def _ApplySafeSyncRev(self, dep):
949 """Finds a valid revision from the content of the safesync_url and apply it
950 by appending revisions to the revision list. Throws if revision appears to
951 be invalid for the given |dep|."""
952 assert len(dep.safesync_url) > 0
953 handle = urllib.urlopen(dep.safesync_url)
954 rev = handle.read().strip()
955 handle.close()
956 if not rev:
957 raise gclient_utils.Error(
958 'It appears your safesync_url (%s) is not working properly\n'
959 '(as it returned an empty response). Check your config.' %
960 dep.safesync_url)
961 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
962 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
963 if self._options.verbose:
964 print('Using safesync_url revision: %s.\n' % safe_rev)
965 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
966
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000967 def RunOnDeps(self, command, args):
968 """Runs a command on each dependency in a client and its dependencies.
969
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000970 Args:
971 command: The command to use (e.g., 'status' or 'diff')
972 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000973 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000974 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000975 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000976 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000977 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000978 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000979 if (command in ('update', 'revert') and sys.stdout.isatty() and not
980 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000981 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000982 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000983 for s in self.dependencies:
984 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000985 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000986
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000987 # Once all the dependencies have been processed, it's now safe to run the
988 # hooks.
989 if not self._options.nohooks:
990 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000991
992 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000993 # Notify the user if there is an orphaned entry in their working copy.
994 # Only delete the directory if there are no changes in it, and
995 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000996 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000997 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000998 if not prev_url:
999 # entry must have been overridden via .gclient custom_deps
1000 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001001 # Fix path separator on Windows.
1002 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001003 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001004 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001005 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001006 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001007 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001008 scm.status(self._options, [], file_list)
1009 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001010 if (not self._options.delete_unversioned_trees or
1011 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001012 # There are modified files in this entry. Keep warning until
1013 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001014 print(('\nWARNING: \'%s\' is no longer part of this client. '
1015 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001016 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001017 else:
1018 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001019 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001020 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001021 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001022 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001023 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001024 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001025
1026 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001027 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001028 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001029 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001030 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001031 for s in self.dependencies:
1032 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001033 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001034
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001035 def GetURLAndRev(dep):
1036 """Returns the revision-qualified SCM url for a Dependency."""
1037 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001038 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001039 if isinstance(dep.parsed_url, self.FileImpl):
1040 original_url = dep.parsed_url.file_location
1041 else:
1042 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001043 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001044 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001045 if not os.path.isdir(scm.checkout_path):
1046 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001047 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001048
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001049 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001050 new_gclient = ''
1051 # First level at .gclient
1052 for d in self.dependencies:
1053 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001054 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001055 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001056 for d in dep.dependencies:
1057 entries[d.name] = GetURLAndRev(d)
1058 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001059 GrabDeps(d)
1060 custom_deps = []
1061 for k in sorted(entries.keys()):
1062 if entries[k]:
1063 # Quotes aren't escaped...
1064 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1065 else:
1066 custom_deps.append(' \"%s\": None,\n' % k)
1067 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1068 'solution_name': d.name,
1069 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001070 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001071 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001072 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001073 'solution_deps': ''.join(custom_deps),
1074 }
1075 # Print the snapshot configuration file
1076 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001077 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001078 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001079 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001080 if self._options.actual:
1081 entries[d.name] = GetURLAndRev(d)
1082 else:
1083 entries[d.name] = d.parsed_url
1084 keys = sorted(entries.keys())
1085 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001086 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001087 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001088
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001089 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001090 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001091 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001092
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001093 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001094 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001095 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001096 return self._root_dir
1097
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001098 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001099 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001100 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001101 return self._enforced_os
1102
maruel@chromium.org68988972011-09-20 14:11:42 +00001103 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001104 def recursion_limit(self):
1105 """How recursive can each dependencies in DEPS file can load DEPS file."""
1106 return self._recursion_limit
1107
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001108
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001109#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001110
1111
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001112def CMDcleanup(parser, args):
1113 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001114
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001115Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001116"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001117 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1118 help='override deps for the specified (comma-separated) '
1119 'platform(s); \'all\' will process all deps_os '
1120 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001121 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001122 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001123 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001124 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001125 if options.verbose:
1126 # Print out the .gclient file. This is longer than if we just printed the
1127 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001128 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129 return client.RunOnDeps('cleanup', args)
1130
1131
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001132@attr('usage', '[command] [args ...]')
1133def CMDrecurse(parser, args):
1134 """Operates on all the entries.
1135
1136 Runs a shell command on all entries.
1137 """
1138 # Stop parsing at the first non-arg so that these go through to the command
1139 parser.disable_interspersed_args()
1140 parser.add_option('-s', '--scm', action='append', default=[],
1141 help='choose scm types to operate upon')
maruel@chromium.orgf2ff9482011-10-19 19:45:54 +00001142 parser.remove_option('--jobs')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001143 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001144 if not args:
1145 print >> sys.stderr, 'Need to supply a command!'
1146 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001147 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1148 if not root_and_entries:
1149 print >> sys.stderr, (
1150 'You need to run gclient sync at least once to use \'recurse\'.\n'
1151 'This is because .gclient_entries needs to exist and be up to date.')
1152 return 1
1153 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001154 scm_set = set()
1155 for scm in options.scm:
1156 scm_set.update(scm.split(','))
1157
1158 # Pass in the SCM type as an env variable
1159 env = os.environ.copy()
1160
1161 for path, url in entries.iteritems():
1162 scm = gclient_scm.GetScmName(url)
1163 if scm_set and scm not in scm_set:
1164 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001165 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001166 if scm:
1167 env['GCLIENT_SCM'] = scm
1168 if url:
1169 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001170 if os.path.isdir(cwd):
1171 subprocess2.call(args, cwd=cwd, env=env)
1172 else:
1173 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001174 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001175
1176
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001177@attr('usage', '[url] [safesync url]')
1178def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001179 """Create a .gclient file in the current directory.
1180
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001181This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001182top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001183modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001184provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001185URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001186"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001187 parser.add_option('--spec',
1188 help='create a gclient file containing the provided '
1189 'string. Due to Cygwin/Python brokenness, it '
1190 'probably can\'t contain any newlines.')
1191 parser.add_option('--name',
1192 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001193 parser.add_option('--deps-file', default='DEPS',
1194 help='overrides the default name for the DEPS file for the'
1195 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001196 parser.add_option('--unmanaged', action='store_true', default=False,
1197 help='overrides the default behavior to make it possible '
1198 'to have the main solution untouched by gclient '
1199 '(gclient will check out unmanaged dependencies but '
1200 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001201 parser.add_option('--git-deps', action='store_true',
1202 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001203 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001204 if ((options.spec and args) or len(args) > 2 or
1205 (not options.spec and not args)):
1206 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1207
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001208 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001209 if options.spec:
1210 client.SetConfig(options.spec)
1211 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001212 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001213 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001214 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001215 if name.endswith('.git'):
1216 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001217 else:
1218 # specify an alternate relpath for the given URL.
1219 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001220 deps_file = options.deps_file
1221 if options.git_deps:
1222 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001223 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001224 if len(args) > 1:
1225 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001226 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1227 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001228 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001229 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001230
1231
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001232@attr('epilog', """Example:
1233 gclient pack > patch.txt
1234 generate simple patch for configured client and dependences
1235""")
1236def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001237 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001238
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001239Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001240dependencies, and performs minimal postprocessing of the output. The
1241resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001242checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001243"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001244 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1245 help='override deps for the specified (comma-separated) '
1246 'platform(s); \'all\' will process all deps_os '
1247 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001248 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001249 client = GClient.LoadCurrentConfig(options)
1250 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001251 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001252 if options.verbose:
1253 # Print out the .gclient file. This is longer than if we just printed the
1254 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001255 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001256 return client.RunOnDeps('pack', args)
1257
1258
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001259def CMDstatus(parser, args):
1260 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001261 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1262 help='override deps for the specified (comma-separated) '
1263 'platform(s); \'all\' will process all deps_os '
1264 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001265 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001266 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001267 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001268 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001269 if options.verbose:
1270 # Print out the .gclient file. This is longer than if we just printed the
1271 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001272 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001273 return client.RunOnDeps('status', args)
1274
1275
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001276@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001277 gclient sync
1278 update files from SCM according to current configuration,
1279 *for modules which have changed since last update or sync*
1280 gclient sync --force
1281 update files from SCM according to current configuration, for
1282 all modules (useful for recovering files deleted from local copy)
1283 gclient sync --revision src@31000
1284 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001285""")
1286def CMDsync(parser, args):
1287 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001288 parser.add_option('-f', '--force', action='store_true',
1289 help='force update even for unchanged modules')
1290 parser.add_option('-n', '--nohooks', action='store_true',
1291 help='don\'t run hooks after the update is complete')
1292 parser.add_option('-r', '--revision', action='append',
1293 dest='revisions', metavar='REV', default=[],
1294 help='Enforces revision/hash for the solutions with the '
1295 'format src@rev. The src@ part is optional and can be '
1296 'skipped. -r can be used multiple times when .gclient '
1297 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001298 'if the src@ part is skipped. Note that specifying '
1299 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001300 parser.add_option('-t', '--transitive', action='store_true',
1301 help='When a revision is specified (in the DEPS file or '
1302 'with the command-line flag), transitively update '
1303 'the dependencies to the date of the given revision. '
1304 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001305 parser.add_option('-H', '--head', action='store_true',
1306 help='skips any safesync_urls specified in '
1307 'configured solutions and sync to head instead')
1308 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001309 help='delete any dependency that have been removed from '
1310 'last sync as long as there is no local modification. '
1311 'Coupled with --force, it will remove them even with '
1312 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001313 parser.add_option('-R', '--reset', action='store_true',
1314 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001315 parser.add_option('-M', '--merge', action='store_true',
1316 help='merge upstream changes instead of trying to '
1317 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001318 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1319 help='override deps for the specified (comma-separated) '
1320 'platform(s); \'all\' will process all deps_os '
1321 'references')
1322 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1323 help='Skip svn up whenever possible by requesting '
1324 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001325 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001326 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001327
1328 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001329 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001330
maruel@chromium.org307d1792010-05-31 20:03:13 +00001331 if options.revisions and options.head:
1332 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001333 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001334
1335 if options.verbose:
1336 # Print out the .gclient file. This is longer than if we just printed the
1337 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001338 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001339 return client.RunOnDeps('update', args)
1340
1341
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001342def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001343 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001344 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001345
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001346def CMDdiff(parser, args):
1347 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001348 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1349 help='override deps for the specified (comma-separated) '
1350 'platform(s); \'all\' will process all deps_os '
1351 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001352 (options, args) = parser.parse_args(args)
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 if options.verbose:
1357 # Print out the .gclient file. This is longer than if we just printed the
1358 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001359 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001360 return client.RunOnDeps('diff', args)
1361
1362
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001363def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001364 """Revert all modifications in every dependencies.
1365
1366 That's the nuclear option to get back to a 'clean' state. It removes anything
1367 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001368 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1369 help='override deps for the specified (comma-separated) '
1370 'platform(s); \'all\' will process all deps_os '
1371 'references')
1372 parser.add_option('-n', '--nohooks', action='store_true',
1373 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001374 (options, args) = parser.parse_args(args)
1375 # --force is implied.
1376 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001377 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001378 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001379 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001380 return client.RunOnDeps('revert', args)
1381
1382
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001383def CMDrunhooks(parser, args):
1384 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001385 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1386 help='override deps for the specified (comma-separated) '
1387 'platform(s); \'all\' will process all deps_os '
1388 'references')
1389 parser.add_option('-f', '--force', action='store_true', default=True,
1390 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001391 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001392 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001393 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001394 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001395 if options.verbose:
1396 # Print out the .gclient file. This is longer than if we just printed the
1397 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001398 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001399 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001400 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001401 return client.RunOnDeps('runhooks', args)
1402
1403
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001404def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001405 """Output revision info mapping for the client and its dependencies.
1406
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001407 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001408 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001409 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1410 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001411 commit can change.
1412 """
1413 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1414 help='override deps for the specified (comma-separated) '
1415 'platform(s); \'all\' will process all deps_os '
1416 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001417 parser.add_option('-a', '--actual', action='store_true',
1418 help='gets the actual checked out revisions instead of the '
1419 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001420 parser.add_option('-s', '--snapshot', action='store_true',
1421 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001422 'version of all repositories to reproduce the tree, '
1423 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001424 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001425 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001426 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001427 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001428 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001429 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001430
1431
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001432def Command(name):
1433 return getattr(sys.modules[__name__], 'CMD' + name, None)
1434
1435
1436def CMDhelp(parser, args):
1437 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001438 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001439 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001440 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001441 parser.print_help()
1442 return 0
1443
1444
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001445def GenUsage(parser, command):
1446 """Modify an OptParse object with the function's documentation."""
1447 obj = Command(command)
1448 if command == 'help':
1449 command = '<command>'
1450 # OptParser.description prefer nicely non-formatted strings.
1451 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1452 usage = getattr(obj, 'usage', '')
1453 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1454 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001455
1456
maruel@chromium.org0895b752011-08-26 20:40:33 +00001457def Parser():
1458 """Returns the default parser."""
1459 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org41071612011-10-19 19:58:08 +00001460 # cygwin has issues with parallel sync
1461 jobs = 1 if sys.platform == 'cygwin' else 8
1462 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001463 help='Specify how many SCM commands can run in parallel; '
1464 'default=%default')
1465 parser.add_option('-v', '--verbose', action='count', default=0,
1466 help='Produces additional output for diagnostics. Can be '
1467 'used up to three times for more logging info.')
1468 parser.add_option('--gclientfile', dest='config_filename',
1469 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1470 help='Specify an alternate %default file')
1471 # Integrate standard options processing.
1472 old_parser = parser.parse_args
1473 def Parse(args):
1474 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001475 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1476 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001477 logging.basicConfig(level=level,
1478 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1479 options.entries_filename = options.config_filename + '_entries'
1480 if options.jobs < 1:
1481 parser.error('--jobs must be 1 or higher')
1482
1483 # These hacks need to die.
1484 if not hasattr(options, 'revisions'):
1485 # GClient.RunOnDeps expects it even if not applicable.
1486 options.revisions = []
1487 if not hasattr(options, 'head'):
1488 options.head = None
1489 if not hasattr(options, 'nohooks'):
1490 options.nohooks = True
1491 if not hasattr(options, 'deps_os'):
1492 options.deps_os = None
1493 if not hasattr(options, 'manually_grab_svn_rev'):
1494 options.manually_grab_svn_rev = None
1495 if not hasattr(options, 'force'):
1496 options.force = None
1497 return (options, args)
1498 parser.parse_args = Parse
1499 # We don't want wordwrapping in epilog (usually examples)
1500 parser.format_epilog = lambda _: parser.epilog or ''
1501 return parser
1502
1503
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001504def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001505 """Doesn't parse the arguments here, just find the right subcommand to
1506 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001507 if sys.hexversion < 0x02050000:
1508 print >> sys.stderr, (
1509 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001510 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001511 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001512 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1513 # operations. Python as a strong tendency to buffer sys.stdout.
1514 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001515 # Make stdout annotated with the thread ids.
1516 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001517 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001518 # Unused variable 'usage'
1519 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001520 def to_str(fn):
1521 return (
1522 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1523 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1524 cmds = (
1525 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1526 )
1527 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001528 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001529 if argv:
1530 command = Command(argv[0])
1531 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001532 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001533 GenUsage(parser, argv[0])
1534 return command(parser, argv[1:])
1535 # Not a known command. Default to help.
1536 GenUsage(parser, 'help')
1537 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001538 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001539 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001540 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001541
1542
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001543if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001544 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001545 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001546
1547# vim: ts=2:sw=2:tw=80:et: