blob: e29eaa80c0fa51bddd3792ba7f141e0382bd61fe [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
maruel@chromium.org36ac2392011-10-12 16:36:11 +000052__version__ = "0.6.3"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000054import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000055import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056import optparse
57import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000058import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000059import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urllib
64
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000065import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066
maruel@chromium.org35625c72011-03-23 17:34:02 +000067import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000068import gclient_scm
69import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000070from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000071import subprocess2
maruel@chromium.org037bd182011-10-21 12:55:14 +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:
923 handle = urllib.urlopen(s.safesync_url)
924 rev = handle.read().strip()
925 handle.close()
926 if len(rev):
927 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000928 if not self._options.revisions:
929 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000930 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000931 index = 0
932 for revision in self._options.revisions:
933 if not '@' in revision:
934 # Support for --revision 123
935 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000936 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000937 if not sol in solutions_names:
938 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
939 print >> sys.stderr, ('Please fix your script, having invalid '
940 '--revision flags will soon considered an error.')
941 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000942 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000943 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000944 return revision_overrides
945
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000946 def RunOnDeps(self, command, args):
947 """Runs a command on each dependency in a client and its dependencies.
948
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949 Args:
950 command: The command to use (e.g., 'status' or 'diff')
951 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000952 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000953 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000954 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000955 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000956 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000957 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000958 if (command in ('update', 'revert') and sys.stdout.isatty() and not
959 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000960 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000961 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000962 for s in self.dependencies:
963 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000964 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000965
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000966 # Once all the dependencies have been processed, it's now safe to run the
967 # hooks.
968 if not self._options.nohooks:
969 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000970
971 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000972 # Notify the user if there is an orphaned entry in their working copy.
973 # Only delete the directory if there are no changes in it, and
974 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000975 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000976 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000977 if not prev_url:
978 # entry must have been overridden via .gclient custom_deps
979 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000980 # Fix path separator on Windows.
981 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000982 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000983 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000984 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000985 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000986 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000987 scm.status(self._options, [], file_list)
988 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000989 if (not self._options.delete_unversioned_trees or
990 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000991 # There are modified files in this entry. Keep warning until
992 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000993 print(('\nWARNING: \'%s\' is no longer part of this client. '
994 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000995 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000996 else:
997 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000998 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000999 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001000 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001001 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001002 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001003 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001004
1005 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001006 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001007 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001008 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001009 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001010 for s in self.dependencies:
1011 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001012 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001013
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001014 def GetURLAndRev(dep):
1015 """Returns the revision-qualified SCM url for a Dependency."""
1016 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001017 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001018 if isinstance(dep.parsed_url, self.FileImpl):
1019 original_url = dep.parsed_url.file_location
1020 else:
1021 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001022 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001023 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001024 if not os.path.isdir(scm.checkout_path):
1025 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001026 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001027
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001028 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001029 new_gclient = ''
1030 # First level at .gclient
1031 for d in self.dependencies:
1032 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001033 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001034 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001035 for d in dep.dependencies:
1036 entries[d.name] = GetURLAndRev(d)
1037 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001038 GrabDeps(d)
1039 custom_deps = []
1040 for k in sorted(entries.keys()):
1041 if entries[k]:
1042 # Quotes aren't escaped...
1043 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1044 else:
1045 custom_deps.append(' \"%s\": None,\n' % k)
1046 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1047 'solution_name': d.name,
1048 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001049 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001050 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001051 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001052 'solution_deps': ''.join(custom_deps),
1053 }
1054 # Print the snapshot configuration file
1055 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001056 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001057 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001058 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001059 if self._options.actual:
1060 entries[d.name] = GetURLAndRev(d)
1061 else:
1062 entries[d.name] = d.parsed_url
1063 keys = sorted(entries.keys())
1064 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001065 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001066 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001067
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001068 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001069 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001070 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001071
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001072 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001073 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001074 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001075 return self._root_dir
1076
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001077 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001078 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001079 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001080 return self._enforced_os
1081
maruel@chromium.org68988972011-09-20 14:11:42 +00001082 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001083 def recursion_limit(self):
1084 """How recursive can each dependencies in DEPS file can load DEPS file."""
1085 return self._recursion_limit
1086
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001087
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001088#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001089
1090
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001091def CMDcleanup(parser, args):
1092 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001093
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001094Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001095"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001096 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1097 help='override deps for the specified (comma-separated) '
1098 'platform(s); \'all\' will process all deps_os '
1099 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001101 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001103 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001104 if options.verbose:
1105 # Print out the .gclient file. This is longer than if we just printed the
1106 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001107 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001108 return client.RunOnDeps('cleanup', args)
1109
1110
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001111@attr('usage', '[command] [args ...]')
1112def CMDrecurse(parser, args):
1113 """Operates on all the entries.
1114
1115 Runs a shell command on all entries.
1116 """
1117 # Stop parsing at the first non-arg so that these go through to the command
1118 parser.disable_interspersed_args()
1119 parser.add_option('-s', '--scm', action='append', default=[],
1120 help='choose scm types to operate upon')
maruel@chromium.orgf2ff9482011-10-19 19:45:54 +00001121 parser.remove_option('--jobs')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001122 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001123 if not args:
1124 print >> sys.stderr, 'Need to supply a command!'
1125 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001126 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1127 if not root_and_entries:
1128 print >> sys.stderr, (
1129 'You need to run gclient sync at least once to use \'recurse\'.\n'
1130 'This is because .gclient_entries needs to exist and be up to date.')
1131 return 1
1132 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001133 scm_set = set()
1134 for scm in options.scm:
1135 scm_set.update(scm.split(','))
1136
1137 # Pass in the SCM type as an env variable
1138 env = os.environ.copy()
1139
1140 for path, url in entries.iteritems():
1141 scm = gclient_scm.GetScmName(url)
1142 if scm_set and scm not in scm_set:
1143 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001144 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001145 if scm:
1146 env['GCLIENT_SCM'] = scm
1147 if url:
1148 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001149 if os.path.isdir(cwd):
1150 subprocess2.call(args, cwd=cwd, env=env)
1151 else:
1152 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001153 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001154
1155
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001156@attr('usage', '[url] [safesync url]')
1157def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001158 """Create a .gclient file in the current directory.
1159
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001160This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001161top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001162modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001163provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001164URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001165"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001166 parser.add_option('--spec',
1167 help='create a gclient file containing the provided '
1168 'string. Due to Cygwin/Python brokenness, it '
1169 'probably can\'t contain any newlines.')
1170 parser.add_option('--name',
1171 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001172 parser.add_option('--deps-file', default='DEPS',
1173 help='overrides the default name for the DEPS file for the'
1174 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001175 parser.add_option('--unmanaged', action='store_true', default=False,
1176 help='overrides the default behavior to make it possible '
1177 'to have the main solution untouched by gclient '
1178 '(gclient will check out unmanaged dependencies but '
1179 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001180 parser.add_option('--git-deps', action='store_true',
1181 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001182 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001183 if ((options.spec and args) or len(args) > 2 or
1184 (not options.spec and not args)):
1185 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1186
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001187 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188 if options.spec:
1189 client.SetConfig(options.spec)
1190 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001191 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001192 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001193 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001194 if name.endswith('.git'):
1195 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001196 else:
1197 # specify an alternate relpath for the given URL.
1198 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001199 deps_file = options.deps_file
1200 if options.git_deps:
1201 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001202 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001203 if len(args) > 1:
1204 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001205 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1206 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001207 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001208 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001209
1210
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001211@attr('epilog', """Example:
1212 gclient pack > patch.txt
1213 generate simple patch for configured client and dependences
1214""")
1215def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001216 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001217
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001218Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001219dependencies, and performs minimal postprocessing of the output. The
1220resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001221checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001222"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001223 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1224 help='override deps for the specified (comma-separated) '
1225 'platform(s); \'all\' will process all deps_os '
1226 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001227 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001228 client = GClient.LoadCurrentConfig(options)
1229 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001230 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001231 if options.verbose:
1232 # Print out the .gclient file. This is longer than if we just printed the
1233 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001234 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001235 return client.RunOnDeps('pack', args)
1236
1237
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001238def CMDstatus(parser, args):
1239 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001240 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1241 help='override deps for the specified (comma-separated) '
1242 'platform(s); \'all\' will process all deps_os '
1243 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001244 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001245 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001246 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001247 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001248 if options.verbose:
1249 # Print out the .gclient file. This is longer than if we just printed the
1250 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001251 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001252 return client.RunOnDeps('status', args)
1253
1254
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001255@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001256 gclient sync
1257 update files from SCM according to current configuration,
1258 *for modules which have changed since last update or sync*
1259 gclient sync --force
1260 update files from SCM according to current configuration, for
1261 all modules (useful for recovering files deleted from local copy)
1262 gclient sync --revision src@31000
1263 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001264""")
1265def CMDsync(parser, args):
1266 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001267 parser.add_option('-f', '--force', action='store_true',
1268 help='force update even for unchanged modules')
1269 parser.add_option('-n', '--nohooks', action='store_true',
1270 help='don\'t run hooks after the update is complete')
1271 parser.add_option('-r', '--revision', action='append',
1272 dest='revisions', metavar='REV', default=[],
1273 help='Enforces revision/hash for the solutions with the '
1274 'format src@rev. The src@ part is optional and can be '
1275 'skipped. -r can be used multiple times when .gclient '
1276 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001277 'if the src@ part is skipped. Note that specifying '
1278 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001279 parser.add_option('-t', '--transitive', action='store_true',
1280 help='When a revision is specified (in the DEPS file or '
1281 'with the command-line flag), transitively update '
1282 'the dependencies to the date of the given revision. '
1283 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001284 parser.add_option('-H', '--head', action='store_true',
1285 help='skips any safesync_urls specified in '
1286 'configured solutions and sync to head instead')
1287 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001288 help='delete any dependency that have been removed from '
1289 'last sync as long as there is no local modification. '
1290 'Coupled with --force, it will remove them even with '
1291 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001292 parser.add_option('-R', '--reset', action='store_true',
1293 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001294 parser.add_option('-M', '--merge', action='store_true',
1295 help='merge upstream changes instead of trying to '
1296 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001297 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1298 help='override deps for the specified (comma-separated) '
1299 'platform(s); \'all\' will process all deps_os '
1300 'references')
1301 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1302 help='Skip svn up whenever possible by requesting '
1303 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001304 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001305 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001306
1307 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001308 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001309
maruel@chromium.org307d1792010-05-31 20:03:13 +00001310 if options.revisions and options.head:
1311 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001312 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001313
1314 if options.verbose:
1315 # Print out the .gclient file. This is longer than if we just printed the
1316 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001317 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001318 return client.RunOnDeps('update', args)
1319
1320
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001321def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001322 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001323 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001324
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001325def CMDdiff(parser, args):
1326 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001327 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1328 help='override deps for the specified (comma-separated) '
1329 'platform(s); \'all\' will process all deps_os '
1330 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001331 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001332 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001333 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001334 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001335 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('diff', args)
1340
1341
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001342def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001343 """Revert all modifications in every dependencies.
1344
1345 That's the nuclear option to get back to a 'clean' state. It removes anything
1346 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001347 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1348 help='override deps for the specified (comma-separated) '
1349 'platform(s); \'all\' will process all deps_os '
1350 'references')
1351 parser.add_option('-n', '--nohooks', action='store_true',
1352 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001353 (options, args) = parser.parse_args(args)
1354 # --force is implied.
1355 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001356 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001357 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001358 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001359 return client.RunOnDeps('revert', args)
1360
1361
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001362def CMDrunhooks(parser, args):
1363 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001364 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1365 help='override deps for the specified (comma-separated) '
1366 'platform(s); \'all\' will process all deps_os '
1367 'references')
1368 parser.add_option('-f', '--force', action='store_true', default=True,
1369 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001370 (options, args) = parser.parse_args(args)
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 if options.verbose:
1375 # Print out the .gclient file. This is longer than if we just printed the
1376 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001377 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001378 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001379 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001380 return client.RunOnDeps('runhooks', args)
1381
1382
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001383def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001384 """Output revision info mapping for the client and its dependencies.
1385
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001386 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001387 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001388 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1389 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001390 commit can change.
1391 """
1392 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1393 help='override deps for the specified (comma-separated) '
1394 'platform(s); \'all\' will process all deps_os '
1395 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001396 parser.add_option('-a', '--actual', action='store_true',
1397 help='gets the actual checked out revisions instead of the '
1398 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001399 parser.add_option('-s', '--snapshot', action='store_true',
1400 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001401 'version of all repositories to reproduce the tree, '
1402 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001403 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001404 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001405 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001406 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001407 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001408 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001409
1410
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001411def Command(name):
1412 return getattr(sys.modules[__name__], 'CMD' + name, None)
1413
1414
1415def CMDhelp(parser, args):
1416 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001417 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001418 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001419 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001420 parser.print_help()
1421 return 0
1422
1423
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001424def GenUsage(parser, command):
1425 """Modify an OptParse object with the function's documentation."""
1426 obj = Command(command)
1427 if command == 'help':
1428 command = '<command>'
1429 # OptParser.description prefer nicely non-formatted strings.
1430 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1431 usage = getattr(obj, 'usage', '')
1432 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1433 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001434
1435
maruel@chromium.org0895b752011-08-26 20:40:33 +00001436def Parser():
1437 """Returns the default parser."""
1438 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org41071612011-10-19 19:58:08 +00001439 # cygwin has issues with parallel sync
1440 jobs = 1 if sys.platform == 'cygwin' else 8
1441 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001442 help='Specify how many SCM commands can run in parallel; '
1443 'default=%default')
1444 parser.add_option('-v', '--verbose', action='count', default=0,
1445 help='Produces additional output for diagnostics. Can be '
1446 'used up to three times for more logging info.')
1447 parser.add_option('--gclientfile', dest='config_filename',
1448 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1449 help='Specify an alternate %default file')
1450 # Integrate standard options processing.
1451 old_parser = parser.parse_args
1452 def Parse(args):
1453 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001454 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1455 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001456 logging.basicConfig(level=level,
1457 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1458 options.entries_filename = options.config_filename + '_entries'
1459 if options.jobs < 1:
1460 parser.error('--jobs must be 1 or higher')
1461
1462 # These hacks need to die.
1463 if not hasattr(options, 'revisions'):
1464 # GClient.RunOnDeps expects it even if not applicable.
1465 options.revisions = []
1466 if not hasattr(options, 'head'):
1467 options.head = None
1468 if not hasattr(options, 'nohooks'):
1469 options.nohooks = True
1470 if not hasattr(options, 'deps_os'):
1471 options.deps_os = None
1472 if not hasattr(options, 'manually_grab_svn_rev'):
1473 options.manually_grab_svn_rev = None
1474 if not hasattr(options, 'force'):
1475 options.force = None
1476 return (options, args)
1477 parser.parse_args = Parse
1478 # We don't want wordwrapping in epilog (usually examples)
1479 parser.format_epilog = lambda _: parser.epilog or ''
1480 return parser
1481
1482
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001483def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001484 """Doesn't parse the arguments here, just find the right subcommand to
1485 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001486 if sys.hexversion < 0x02050000:
1487 print >> sys.stderr, (
1488 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org037bd182011-10-21 12:55:14 +00001489 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001490 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001491 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1492 # operations. Python as a strong tendency to buffer sys.stdout.
1493 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001494 # Make stdout annotated with the thread ids.
1495 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001496 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001497 # Unused variable 'usage'
1498 # pylint: disable=W0612
maruel@chromium.org037bd182011-10-21 12:55:14 +00001499 def to_str(fn):
1500 return (
1501 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1502 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1503 cmds = (
1504 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1505 )
1506 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001507 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001508 if argv:
1509 command = Command(argv[0])
1510 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001511 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001512 GenUsage(parser, argv[0])
1513 return command(parser, argv[1:])
1514 # Not a known command. Default to help.
1515 GenUsage(parser, 'help')
1516 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001517 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001518 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001519 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001520
1521
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001522if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001523 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001524 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001525
1526# vim: ts=2:sw=2:tw=80:et: