blob: a9b14bf9ac4981bbf8a8fe0fd8a7b9af1bfa201e [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)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000933 if not sol in solutions_names:
934 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
935 print >> sys.stderr, ('Please fix your script, having invalid '
936 '--revision flags will soon considered an error.')
937 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000938 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000939 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000940 return revision_overrides
941
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000942 def _ApplySafeSyncRev(self, dep):
943 """Finds a valid revision from the content of the safesync_url and apply it
944 by appending revisions to the revision list. Throws if revision appears to
945 be invalid for the given |dep|."""
946 assert len(dep.safesync_url) > 0
947 handle = urllib.urlopen(dep.safesync_url)
948 rev = handle.read().strip()
949 handle.close()
950 if not rev:
951 raise gclient_utils.Error(
952 'It appears your safesync_url (%s) is not working properly\n'
953 '(as it returned an empty response). Check your config.' %
954 dep.safesync_url)
955 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
956 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
957 if self._options.verbose:
958 print('Using safesync_url revision: %s.\n' % safe_rev)
959 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
960
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000961 def RunOnDeps(self, command, args):
962 """Runs a command on each dependency in a client and its dependencies.
963
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000964 Args:
965 command: The command to use (e.g., 'status' or 'diff')
966 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000967 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000968 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000969 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000970 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000971 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000972 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000973 if (command in ('update', 'revert') and sys.stdout.isatty() and not
974 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000975 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000976 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000977 for s in self.dependencies:
978 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000979 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000980
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000981 # Once all the dependencies have been processed, it's now safe to run the
982 # hooks.
983 if not self._options.nohooks:
984 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000985
986 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000987 # Notify the user if there is an orphaned entry in their working copy.
988 # Only delete the directory if there are no changes in it, and
989 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000990 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000991 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000992 if not prev_url:
993 # entry must have been overridden via .gclient custom_deps
994 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000995 # Fix path separator on Windows.
996 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000997 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000998 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000999 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001000 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001001 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001002 scm.status(self._options, [], file_list)
1003 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001004 if (not self._options.delete_unversioned_trees or
1005 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001006 # There are modified files in this entry. Keep warning until
1007 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001008 print(('\nWARNING: \'%s\' is no longer part of this client. '
1009 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001010 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001011 else:
1012 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001013 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001014 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001015 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001016 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001017 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001018 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001019
1020 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001021 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001022 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001023 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001024 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001025 for s in self.dependencies:
1026 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001027 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001028
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001029 def GetURLAndRev(dep):
1030 """Returns the revision-qualified SCM url for a Dependency."""
1031 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001032 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001033 if isinstance(dep.parsed_url, self.FileImpl):
1034 original_url = dep.parsed_url.file_location
1035 else:
1036 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001037 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001038 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001039 if not os.path.isdir(scm.checkout_path):
1040 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001041 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001043 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001044 new_gclient = ''
1045 # First level at .gclient
1046 for d in self.dependencies:
1047 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001048 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001049 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001050 for d in dep.dependencies:
1051 entries[d.name] = GetURLAndRev(d)
1052 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001053 GrabDeps(d)
1054 custom_deps = []
1055 for k in sorted(entries.keys()):
1056 if entries[k]:
1057 # Quotes aren't escaped...
1058 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1059 else:
1060 custom_deps.append(' \"%s\": None,\n' % k)
1061 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1062 'solution_name': d.name,
1063 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001064 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001065 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001066 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001067 'solution_deps': ''.join(custom_deps),
1068 }
1069 # Print the snapshot configuration file
1070 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001071 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001072 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001073 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001074 if self._options.actual:
1075 entries[d.name] = GetURLAndRev(d)
1076 else:
1077 entries[d.name] = d.parsed_url
1078 keys = sorted(entries.keys())
1079 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001080 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001081 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001082
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001083 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001084 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001085 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001086
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001087 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001088 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001089 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001090 return self._root_dir
1091
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001092 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001093 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001094 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001095 return self._enforced_os
1096
maruel@chromium.org68988972011-09-20 14:11:42 +00001097 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001098 def recursion_limit(self):
1099 """How recursive can each dependencies in DEPS file can load DEPS file."""
1100 return self._recursion_limit
1101
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001103#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001104
1105
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001106def CMDcleanup(parser, args):
1107 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001108
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001109Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001110"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001111 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1112 help='override deps for the specified (comma-separated) '
1113 'platform(s); \'all\' will process all deps_os '
1114 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001115 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001116 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001117 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001118 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001119 if options.verbose:
1120 # Print out the .gclient file. This is longer than if we just printed the
1121 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001122 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001123 return client.RunOnDeps('cleanup', args)
1124
1125
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001126@attr('usage', '[command] [args ...]')
1127def CMDrecurse(parser, args):
1128 """Operates on all the entries.
1129
1130 Runs a shell command on all entries.
1131 """
1132 # Stop parsing at the first non-arg so that these go through to the command
1133 parser.disable_interspersed_args()
1134 parser.add_option('-s', '--scm', action='append', default=[],
1135 help='choose scm types to operate upon')
maruel@chromium.orgf2ff9482011-10-19 19:45:54 +00001136 parser.remove_option('--jobs')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001137 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001138 if not args:
1139 print >> sys.stderr, 'Need to supply a command!'
1140 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001141 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1142 if not root_and_entries:
1143 print >> sys.stderr, (
1144 'You need to run gclient sync at least once to use \'recurse\'.\n'
1145 'This is because .gclient_entries needs to exist and be up to date.')
1146 return 1
1147 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001148 scm_set = set()
1149 for scm in options.scm:
1150 scm_set.update(scm.split(','))
1151
1152 # Pass in the SCM type as an env variable
1153 env = os.environ.copy()
1154
1155 for path, url in entries.iteritems():
1156 scm = gclient_scm.GetScmName(url)
1157 if scm_set and scm not in scm_set:
1158 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001159 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001160 if scm:
1161 env['GCLIENT_SCM'] = scm
1162 if url:
1163 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001164 if os.path.isdir(cwd):
1165 subprocess2.call(args, cwd=cwd, env=env)
1166 else:
1167 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001168 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001169
1170
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001171@attr('usage', '[url] [safesync url]')
1172def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001173 """Create a .gclient file in the current directory.
1174
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001175This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001176top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001177modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001178provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001179URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001180"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001181 parser.add_option('--spec',
1182 help='create a gclient file containing the provided '
1183 'string. Due to Cygwin/Python brokenness, it '
1184 'probably can\'t contain any newlines.')
1185 parser.add_option('--name',
1186 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001187 parser.add_option('--deps-file', default='DEPS',
1188 help='overrides the default name for the DEPS file for the'
1189 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001190 parser.add_option('--unmanaged', action='store_true', default=False,
1191 help='overrides the default behavior to make it possible '
1192 'to have the main solution untouched by gclient '
1193 '(gclient will check out unmanaged dependencies but '
1194 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001195 parser.add_option('--git-deps', action='store_true',
1196 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001197 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001198 if ((options.spec and args) or len(args) > 2 or
1199 (not options.spec and not args)):
1200 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1201
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001202 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001203 if options.spec:
1204 client.SetConfig(options.spec)
1205 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001206 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001207 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001208 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001209 if name.endswith('.git'):
1210 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001211 else:
1212 # specify an alternate relpath for the given URL.
1213 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001214 deps_file = options.deps_file
1215 if options.git_deps:
1216 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001217 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001218 if len(args) > 1:
1219 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001220 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1221 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001222 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001223 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001224
1225
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001226@attr('epilog', """Example:
1227 gclient pack > patch.txt
1228 generate simple patch for configured client and dependences
1229""")
1230def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001231 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001232
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001233Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001234dependencies, and performs minimal postprocessing of the output. The
1235resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001236checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001237"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001238 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1239 help='override deps for the specified (comma-separated) '
1240 'platform(s); \'all\' will process all deps_os '
1241 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001242 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001243 client = GClient.LoadCurrentConfig(options)
1244 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001245 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001246 if options.verbose:
1247 # Print out the .gclient file. This is longer than if we just printed the
1248 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001249 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001250 return client.RunOnDeps('pack', args)
1251
1252
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001253def CMDstatus(parser, args):
1254 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001255 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1256 help='override deps for the specified (comma-separated) '
1257 'platform(s); \'all\' will process all deps_os '
1258 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001259 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001260 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001261 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001262 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001263 if options.verbose:
1264 # Print out the .gclient file. This is longer than if we just printed the
1265 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001266 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001267 return client.RunOnDeps('status', args)
1268
1269
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001270@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001271 gclient sync
1272 update files from SCM according to current configuration,
1273 *for modules which have changed since last update or sync*
1274 gclient sync --force
1275 update files from SCM according to current configuration, for
1276 all modules (useful for recovering files deleted from local copy)
1277 gclient sync --revision src@31000
1278 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001279""")
1280def CMDsync(parser, args):
1281 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001282 parser.add_option('-f', '--force', action='store_true',
1283 help='force update even for unchanged modules')
1284 parser.add_option('-n', '--nohooks', action='store_true',
1285 help='don\'t run hooks after the update is complete')
1286 parser.add_option('-r', '--revision', action='append',
1287 dest='revisions', metavar='REV', default=[],
1288 help='Enforces revision/hash for the solutions with the '
1289 'format src@rev. The src@ part is optional and can be '
1290 'skipped. -r can be used multiple times when .gclient '
1291 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001292 'if the src@ part is skipped. Note that specifying '
1293 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001294 parser.add_option('-t', '--transitive', action='store_true',
1295 help='When a revision is specified (in the DEPS file or '
1296 'with the command-line flag), transitively update '
1297 'the dependencies to the date of the given revision. '
1298 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001299 parser.add_option('-H', '--head', action='store_true',
1300 help='skips any safesync_urls specified in '
1301 'configured solutions and sync to head instead')
1302 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org30159da2012-02-15 17:09:52 +00001303 help='delete any dependency that have been removed from '
1304 'last sync as long as there is no local modification. '
1305 'Coupled with --force, it will remove them even with '
1306 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001307 parser.add_option('-R', '--reset', action='store_true',
1308 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001309 parser.add_option('-M', '--merge', action='store_true',
1310 help='merge upstream changes instead of trying to '
1311 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001312 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1313 help='override deps for the specified (comma-separated) '
1314 'platform(s); \'all\' will process all deps_os '
1315 'references')
1316 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1317 help='Skip svn up whenever possible by requesting '
1318 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001319 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001320 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001321
1322 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001323 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001324
maruel@chromium.org307d1792010-05-31 20:03:13 +00001325 if options.revisions and options.head:
1326 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001327 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001328
1329 if options.verbose:
1330 # Print out the .gclient file. This is longer than if we just printed the
1331 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001332 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001333 return client.RunOnDeps('update', args)
1334
1335
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001336def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001337 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001338 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001339
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001340def CMDdiff(parser, args):
1341 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001342 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1343 help='override deps for the specified (comma-separated) '
1344 'platform(s); \'all\' will process all deps_os '
1345 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001346 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001347 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001348 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001349 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001350 if options.verbose:
1351 # Print out the .gclient file. This is longer than if we just printed the
1352 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001353 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001354 return client.RunOnDeps('diff', args)
1355
1356
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001357def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001358 """Revert all modifications in every dependencies.
1359
1360 That's the nuclear option to get back to a 'clean' state. It removes anything
1361 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001362 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1363 help='override deps for the specified (comma-separated) '
1364 'platform(s); \'all\' will process all deps_os '
1365 'references')
1366 parser.add_option('-n', '--nohooks', action='store_true',
1367 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001368 (options, args) = parser.parse_args(args)
1369 # --force is implied.
1370 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001371 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001372 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001373 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001374 return client.RunOnDeps('revert', args)
1375
1376
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001377def CMDrunhooks(parser, args):
1378 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001379 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1380 help='override deps for the specified (comma-separated) '
1381 'platform(s); \'all\' will process all deps_os '
1382 'references')
1383 parser.add_option('-f', '--force', action='store_true', default=True,
1384 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001385 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001386 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001387 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001388 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001389 if options.verbose:
1390 # Print out the .gclient file. This is longer than if we just printed the
1391 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001392 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001393 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001394 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001395 return client.RunOnDeps('runhooks', args)
1396
1397
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001398def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001399 """Output revision info mapping for the client and its dependencies.
1400
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001401 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001402 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001403 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1404 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001405 commit can change.
1406 """
1407 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1408 help='override deps for the specified (comma-separated) '
1409 'platform(s); \'all\' will process all deps_os '
1410 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001411 parser.add_option('-a', '--actual', action='store_true',
1412 help='gets the actual checked out revisions instead of the '
1413 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001414 parser.add_option('-s', '--snapshot', action='store_true',
1415 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001416 'version of all repositories to reproduce the tree, '
1417 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001418 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001419 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001420 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001421 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001422 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001423 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001424
1425
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001426def Command(name):
1427 return getattr(sys.modules[__name__], 'CMD' + name, None)
1428
1429
1430def CMDhelp(parser, args):
1431 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001432 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001433 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001434 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001435 parser.print_help()
1436 return 0
1437
1438
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001439def GenUsage(parser, command):
1440 """Modify an OptParse object with the function's documentation."""
1441 obj = Command(command)
1442 if command == 'help':
1443 command = '<command>'
1444 # OptParser.description prefer nicely non-formatted strings.
1445 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1446 usage = getattr(obj, 'usage', '')
1447 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1448 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001449
1450
maruel@chromium.org0895b752011-08-26 20:40:33 +00001451def Parser():
1452 """Returns the default parser."""
1453 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org41071612011-10-19 19:58:08 +00001454 # cygwin has issues with parallel sync
1455 jobs = 1 if sys.platform == 'cygwin' else 8
1456 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001457 help='Specify how many SCM commands can run in parallel; '
1458 'default=%default')
1459 parser.add_option('-v', '--verbose', action='count', default=0,
1460 help='Produces additional output for diagnostics. Can be '
1461 'used up to three times for more logging info.')
1462 parser.add_option('--gclientfile', dest='config_filename',
1463 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1464 help='Specify an alternate %default file')
1465 # Integrate standard options processing.
1466 old_parser = parser.parse_args
1467 def Parse(args):
1468 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001469 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1470 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001471 logging.basicConfig(level=level,
1472 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1473 options.entries_filename = options.config_filename + '_entries'
1474 if options.jobs < 1:
1475 parser.error('--jobs must be 1 or higher')
1476
1477 # These hacks need to die.
1478 if not hasattr(options, 'revisions'):
1479 # GClient.RunOnDeps expects it even if not applicable.
1480 options.revisions = []
1481 if not hasattr(options, 'head'):
1482 options.head = None
1483 if not hasattr(options, 'nohooks'):
1484 options.nohooks = True
1485 if not hasattr(options, 'deps_os'):
1486 options.deps_os = None
1487 if not hasattr(options, 'manually_grab_svn_rev'):
1488 options.manually_grab_svn_rev = None
1489 if not hasattr(options, 'force'):
1490 options.force = None
1491 return (options, args)
1492 parser.parse_args = Parse
1493 # We don't want wordwrapping in epilog (usually examples)
1494 parser.format_epilog = lambda _: parser.epilog or ''
1495 return parser
1496
1497
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001498def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001499 """Doesn't parse the arguments here, just find the right subcommand to
1500 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001501 if sys.hexversion < 0x02050000:
1502 print >> sys.stderr, (
1503 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001504 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001505 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001506 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1507 # operations. Python as a strong tendency to buffer sys.stdout.
1508 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001509 # Make stdout annotated with the thread ids.
1510 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001511 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001512 # Unused variable 'usage'
1513 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001514 def to_str(fn):
1515 return (
1516 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1517 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1518 cmds = (
1519 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1520 )
1521 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001522 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001523 if argv:
1524 command = Command(argv[0])
1525 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001526 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001527 GenUsage(parser, argv[0])
1528 return command(parser, argv[1:])
1529 # Not a known command. Default to help.
1530 GenUsage(parser, 'help')
1531 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001532 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001533 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001534 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001535
1536
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001537if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001538 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001539 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001540
1541# vim: ts=2:sw=2:tw=80:et: