blob: 9931ad79116d71d2db38d6cd4fa27ca3965baebb [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.org82798cb2012-02-23 18:16:12 +000052__version__ = "0.6.4"
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.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000171 self._url = self._url.replace('/@', '@')
172 elif not isinstance(self._url,
173 (self.FromImpl, self.FileImpl, None.__class__)):
174 raise gclient_utils.Error(
175 ('dependency url must be either a string, None, '
176 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000177 if '/' in self._deps_file or '\\' in self._deps_file:
178 raise gclient_utils.Error('deps_file name must not be a path, just a '
179 'filename. %s' % self._deps_file)
180
181 @property
182 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000183 return self._deps_file
184
185 @property
186 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000187 return self._managed
188
189 @property
190 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000191 return self._parent
192
193 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000194 def root(self):
195 """Returns the root node, a GClient object."""
196 if not self.parent:
197 # This line is to signal pylint that it could be a GClient instance.
198 return self or GClient(None, None)
199 return self.parent.root
200
201 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000203 return self._safesync_url
204
205 @property
206 def should_process(self):
207 """True if this dependency should be processed, i.e. checked out."""
208 return self._should_process
209
210 @property
211 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000212 return self._custom_vars.copy()
213
214 @property
215 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000216 return self._custom_deps.copy()
217
maruel@chromium.org064186c2011-09-27 23:53:33 +0000218 @property
219 def url(self):
220 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000221
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000222 @property
223 def recursion_limit(self):
224 """Returns > 0 if this dependency is not too recursed to be processed."""
225 return max(self.parent.recursion_limit - 1, 0)
226
227 def get_custom_deps(self, name, url):
228 """Returns a custom deps if applicable."""
229 if self.parent:
230 url = self.parent.get_custom_deps(name, url)
231 # None is a valid return value to disable a dependency.
232 return self.custom_deps.get(name, url)
233
maruel@chromium.org064186c2011-09-27 23:53:33 +0000234
235class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000236 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000237
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000238 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000239 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000240 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000241 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000242 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000244
245 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000246 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000247
248 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000249 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000250 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000251 # A cache of the files affected by the current operation, necessary for
252 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000253 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000254 # If it is not set to True, the dependency wasn't processed for its child
255 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000256 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000257 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000258 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000259 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000260 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000261
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000262 if not self.name and self.parent:
263 raise gclient_utils.Error('Dependency without name')
264
maruel@chromium.org470b5432011-10-11 18:18:19 +0000265 @property
266 def requirements(self):
267 """Calculate the list of requirements."""
268 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000269 # self.parent is implicitly a requirement. This will be recursive by
270 # definition.
271 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000272 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000273
274 # For a tree with at least 2 levels*, the leaf node needs to depend
275 # on the level higher up in an orderly way.
276 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
277 # thus unsorted, while the .gclient format is a list thus sorted.
278 #
279 # * _recursion_limit is hard coded 2 and there is no hope to change this
280 # value.
281 #
282 # Interestingly enough, the following condition only works in the case we
283 # want: self is a 2nd level node. 3nd level node wouldn't need this since
284 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000285 if self.parent and self.parent.parent and not self.parent.parent.parent:
286 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000287
288 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000289 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000290
maruel@chromium.org470b5432011-10-11 18:18:19 +0000291 if self.name:
292 requirements |= set(
293 obj.name for obj in self.root.subtree(False)
294 if (obj is not self
295 and obj.name and
296 self.name.startswith(posixpath.join(obj.name, ''))))
297 requirements = tuple(sorted(requirements))
298 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
299 return requirements
300
301 def verify_validity(self):
302 """Verifies that this Dependency is fine to add as a child of another one.
303
304 Returns True if this entry should be added, False if it is a duplicate of
305 another entry.
306 """
307 logging.info('Dependency(%s).verify_validity()' % self.name)
308 if self.name in [s.name for s in self.parent.dependencies]:
309 raise gclient_utils.Error(
310 'The same name "%s" appears multiple times in the deps section' %
311 self.name)
312 if not self.should_process:
313 # Return early, no need to set requirements.
314 return True
315
316 # This require a full tree traversal with locks.
317 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
318 for sibling in siblings:
319 if self.url != sibling.url:
320 raise gclient_utils.Error(
321 'Dependency %s specified more than once:\n %s\nvs\n %s' %
322 (self.name, sibling.hierarchy(), self.hierarchy()))
323 # In theory we could keep it as a shadow of the other one. In
324 # practice, simply ignore it.
325 logging.warn('Won\'t process duplicate dependency %s' % sibling)
326 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000327 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000328
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000329 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000330 """Resolves the parsed url from url.
331
332 Manages From() keyword accordingly. Do not touch self.parsed_url nor
333 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000334 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000335 parsed_url = self.get_custom_deps(self.name, url)
336 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000337 logging.info(
338 'Dependency(%s).LateOverride(%s) -> %s' %
339 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000340 return parsed_url
341
342 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000343 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000344 ref = [
345 dep for dep in self.root.subtree(True) if url.module_name == dep.name
346 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000347 if not ref:
348 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
349 url.module_name, ref))
350 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000351 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000352 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000353 found_deps = [d for d in ref.dependencies if d.name == sub_target]
354 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000355 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000356 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
357 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000358 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000359
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000360 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000361 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000362 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000363 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000364 'Dependency(%s).LateOverride(%s) -> %s (From)' %
365 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000366 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000367
368 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000369 parsed_url = urlparse.urlparse(url)
370 if not parsed_url[0]:
371 # A relative url. Fetch the real base.
372 path = parsed_url[2]
373 if not path.startswith('/'):
374 raise gclient_utils.Error(
375 'relative DEPS entry \'%s\' must begin with a slash' % url)
376 # Create a scm just to query the full url.
377 parent_url = self.parent.parsed_url
378 if isinstance(parent_url, self.FileImpl):
379 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000380 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000381 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000382 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000383 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000384 logging.info(
385 'Dependency(%s).LateOverride(%s) -> %s' %
386 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000387 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000388
389 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000390 logging.info(
391 'Dependency(%s).LateOverride(%s) -> %s (File)' %
392 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000393 return url
394
395 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000396 logging.info(
397 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000398 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000399
400 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000401
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000402 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000403 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000404 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000405 assert not self.dependencies
406 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000407 local_scope = {}
408 var = self.VarImpl(self.custom_vars, local_scope)
409 global_scope = {
410 'File': self.FileImpl,
411 'From': self.FromImpl,
412 'Var': var.Lookup,
413 'deps_os': {},
414 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000415 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000416 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000417 logging.info(
418 'ParseDepsFile(%s): No %s file found at %s' % (
419 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000420 else:
421 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000422 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000423 # Eval the content.
424 try:
425 exec(deps_content, global_scope, local_scope)
426 except SyntaxError, e:
427 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000428 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000429 # load os specific dependencies if defined. these dependencies may
430 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000431 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000432 enforced_os = self.root.enforced_os
433 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000434 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000435 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000436 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000437 # platform, so we collect the broadest set of dependencies
438 # available. We may end up with the wrong revision of something for
439 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000440 deps.update([x for x in os_deps.items() if not x[0] in deps])
441 else:
442 deps.update(os_deps)
443
maruel@chromium.org271375b2010-06-23 19:17:38 +0000444 # If a line is in custom_deps, but not in the solution, we want to append
445 # this line to the solution.
446 for d in self.custom_deps:
447 if d not in deps:
448 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000449
450 # If use_relative_paths is set in the DEPS file, regenerate
451 # the dictionary using paths relative to the directory containing
452 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000453 use_relative_paths = local_scope.get('use_relative_paths', False)
454 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000455 rel_deps = {}
456 for d, url in deps.items():
457 # normpath is required to allow DEPS to use .. in their
458 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000459 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
460 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000461
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000462 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000463 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000464 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000465 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000466 deps_to_add.append(Dependency(
467 self, name, url, None, None, None, None,
468 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000469 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000470 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
471 logging.info('ParseDepsFile(%s) done' % self.name)
472
473 def add_dependencies_and_close(self, deps_to_add, hooks):
474 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000475 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000476 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000477 self.add_dependency(dep)
478 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000479
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000480 @staticmethod
481 def maybeGetParentRevision(
482 command, options, parsed_url, parent_name, revision_overrides):
483 """If we are performing an update and --transitive is set, set the
484 revision to the parent's revision. If we have an explicit revision
485 do nothing."""
486 if command == 'update' and options.transitive and not options.revision:
487 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
488 if not revision:
489 options.revision = revision_overrides.get(parent_name)
490 if options.verbose and options.revision:
491 print("Using parent's revision date: %s" % options.revision)
492 # If the parent has a revision override, then it must have been
493 # converted to date format.
494 assert (not options.revision or
495 gclient_utils.IsDateRevision(options.revision))
496
497 @staticmethod
498 def maybeConvertToDateRevision(
499 command, options, name, scm, revision_overrides):
500 """If we are performing an update and --transitive is set, convert the
501 revision to a date-revision (if necessary). Instead of having
502 -r 101 replace the revision with the time stamp of 101 (e.g.
503 "{2011-18-04}").
504 This way dependencies are upgraded to the revision they had at the
505 check-in of revision 101."""
506 if (command == 'update' and
507 options.transitive and
508 options.revision and
509 not gclient_utils.IsDateRevision(options.revision)):
510 revision_date = scm.GetRevisionDate(options.revision)
511 revision = gclient_utils.MakeDateRevision(revision_date)
512 if options.verbose:
513 print("Updating revision override from %s to %s." %
514 (options.revision, revision))
515 revision_overrides[name] = revision
516
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000517 # Arguments number differs from overridden method
518 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000519 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000520 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000521 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000522 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000523 if not self.should_process:
524 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000525 # When running runhooks, there's no need to consult the SCM.
526 # All known hooks are expected to run unconditionally regardless of working
527 # copy state, so skip the SCM status check.
528 run_scm = command not in ('runhooks', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000529 parsed_url = self.LateOverride(self.url)
530 file_list = []
531 if run_scm and parsed_url:
532 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000533 # Special support for single-file checkout.
534 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000535 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
536 # pylint: disable=E1103
537 options.revision = parsed_url.GetRevision()
538 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000539 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000540 self.name)
541 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000542 args + [parsed_url.GetFilename()],
543 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000544 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000545 # Create a shallow copy to mutate revision.
546 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000547 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000548 self.maybeGetParentRevision(
549 command, options, parsed_url, self.parent.name, revision_overrides)
550 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
551 scm.RunCommand(command, options, args, file_list)
552 self.maybeConvertToDateRevision(
553 command, options, self.name, scm, revision_overrides)
554 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000555
556 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
557 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000558 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000559 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000560 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000561 continue
562 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000563 [self.root.root_dir.lower(), file_list[i].lower()])
564 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000565 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000566 while file_list[i].startswith(('\\', '/')):
567 file_list[i] = file_list[i][1:]
568
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000569 # Always parse the DEPS file.
570 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000571
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000572 self._run_is_done(file_list, parsed_url)
573
574 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000575 # Parse the dependencies of this dependency.
576 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000577 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000578
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000579 @gclient_utils.lockedmethod
580 def _run_is_done(self, file_list, parsed_url):
581 # Both these are kept for hooks that are run as a separate tree traversal.
582 self._file_list = file_list
583 self._parsed_url = parsed_url
584 self._processed = True
585
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000586 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000587 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000588 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000589 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000590 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000591 # Don't run the hook when it is above recursion_limit.
592 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000593 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000594 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000595 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000596 # TODO(maruel): If the user is using git or git-svn, then we don't know
597 # what files have changed so we always run all hooks. It'd be nice to fix
598 # that.
599 if (options.force or
600 isinstance(self.parsed_url, self.FileImpl) or
601 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000602 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000603 for hook_dict in self.deps_hooks:
604 self._RunHookAction(hook_dict, [])
605 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000606 # Run hooks on the basis of whether the files from the gclient operation
607 # match each hook's pattern.
608 for hook_dict in self.deps_hooks:
609 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000610 matching_file_list = [
611 f for f in self.file_list_and_children if pattern.search(f)
612 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000613 if matching_file_list:
614 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000615 for s in self.dependencies:
616 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000617
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000618 def _RunHookAction(self, hook_dict, matching_file_list):
619 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000620 # A single DEPS file can specify multiple hooks so this function can be
621 # called multiple times on a single Dependency.
622 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000623 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000624 logging.debug(hook_dict)
625 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000626 command = hook_dict['action'][:]
627 if command[0] == 'python':
628 # If the hook specified "python" as the first item, the action is a
629 # Python script. Run it by starting a new copy of the same
630 # interpreter.
631 command[0] = sys.executable
632
633 if '$matching_files' in command:
634 splice_index = command.index('$matching_files')
635 command[splice_index:splice_index + 1] = matching_file_list
636
maruel@chromium.org17d01792010-09-01 18:07:10 +0000637 try:
638 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000639 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000640 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000641 # Use a discrete exit status code of 2 to indicate that a hook action
642 # failed. Users of this script may wish to treat hook action failures
643 # differently from VC failures.
644 print >> sys.stderr, 'Error: %s' % str(e)
645 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000646
maruel@chromium.org0d812442010-08-10 12:41:08 +0000647 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000648 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000649 dependencies = self.dependencies
650 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000651 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000652 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000653 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000654 for i in d.subtree(include_all):
655 yield i
656
657 def depth_first_tree(self):
658 """Depth-first recursion including the root node."""
659 yield self
660 for i in self.dependencies:
661 for j in i.depth_first_tree():
662 if j.should_process:
663 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000664
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000665 @gclient_utils.lockedmethod
666 def add_dependency(self, new_dep):
667 self._dependencies.append(new_dep)
668
669 @gclient_utils.lockedmethod
670 def _mark_as_parsed(self, new_hooks):
671 self._deps_hooks.extend(new_hooks)
672 self._deps_parsed = True
673
maruel@chromium.org68988972011-09-20 14:11:42 +0000674 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000675 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000676 def dependencies(self):
677 return tuple(self._dependencies)
678
679 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000680 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000681 def deps_hooks(self):
682 return tuple(self._deps_hooks)
683
684 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000685 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000686 def parsed_url(self):
687 return self._parsed_url
688
689 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000690 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000691 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000692 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000693 return self._deps_parsed
694
695 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000696 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000697 def processed(self):
698 return self._processed
699
700 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000701 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000702 def hooks_ran(self):
703 return self._hooks_ran
704
705 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000706 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000707 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000708 return tuple(self._file_list)
709
710 @property
711 def file_list_and_children(self):
712 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000713 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000714 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000715 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000716
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000717 def __str__(self):
718 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000719 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000720 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000721 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000722 # First try the native property if it exists.
723 if hasattr(self, '_' + i):
724 value = getattr(self, '_' + i, False)
725 else:
726 value = getattr(self, i, False)
727 if value:
728 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000729
730 for d in self.dependencies:
731 out.extend([' ' + x for x in str(d).splitlines()])
732 out.append('')
733 return '\n'.join(out)
734
735 def __repr__(self):
736 return '%s: %s' % (self.name, self.url)
737
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000738 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000739 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000740 out = '%s(%s)' % (self.name, self.url)
741 i = self.parent
742 while i and i.name:
743 out = '%s(%s) -> %s' % (i.name, i.url, out)
744 i = i.parent
745 return out
746
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000747
748class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000749 """Object that represent a gclient checkout. A tree of Dependency(), one per
750 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000751
752 DEPS_OS_CHOICES = {
753 "win32": "win",
754 "win": "win",
755 "cygwin": "win",
756 "darwin": "mac",
757 "mac": "mac",
758 "unix": "unix",
759 "linux": "unix",
760 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000761 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000762 }
763
764 DEFAULT_CLIENT_FILE_TEXT = ("""\
765solutions = [
766 { "name" : "%(solution_name)s",
767 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000768 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000769 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000770 "custom_deps" : {
771 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000772 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000773 },
774]
775""")
776
777 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
778 { "name" : "%(solution_name)s",
779 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000780 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000781 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000782 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000783%(solution_deps)s },
784 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000785 },
786""")
787
788 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
789# Snapshot generated with gclient revinfo --snapshot
790solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000791%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000792""")
793
794 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000795 # Do not change previous behavior. Only solution level and immediate DEPS
796 # are processed.
797 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000798 Dependency.__init__(self, None, None, None, None, True, None, None,
799 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000800 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000801 if options.deps_os:
802 enforced_os = options.deps_os.split(',')
803 else:
804 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
805 if 'all' in enforced_os:
806 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000807 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000808 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000809 self.config_content = None
810
811 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000812 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000813 config_dict = {}
814 self.config_content = content
815 try:
816 exec(content, config_dict)
817 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000818 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000819
820 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000821 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000822 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000823 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000824 self, s['name'], s['url'],
825 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000826 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000827 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000828 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000829 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000830 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000831 except KeyError:
832 raise gclient_utils.Error('Invalid .gclient file. Solution is '
833 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000834 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
835 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000836
837 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000838 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000839 self._options.config_filename),
840 self.config_content)
841
842 @staticmethod
843 def LoadCurrentConfig(options):
844 """Searches for and loads a .gclient file relative to the current working
845 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000846 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000847 if not path:
848 return None
849 client = GClient(path, options)
850 client.SetConfig(gclient_utils.FileRead(
851 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000852
853 if (options.revisions and
854 len(client.dependencies) > 1 and
855 any('@' not in r for r in options.revisions)):
856 print >> sys.stderr, (
857 'You must specify the full solution name like --revision %s@%s\n'
858 'when you have multiple solutions setup in your .gclient file.\n'
859 'Other solutions present are: %s.') % (
860 client.dependencies[0].name,
861 options.revisions[0],
862 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000863 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000864
nsylvain@google.comefc80932011-05-31 21:27:56 +0000865 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000866 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000867 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
868 'solution_name': solution_name,
869 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000870 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000871 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000872 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000873 })
874
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000875 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000876 """Creates a .gclient_entries file to record the list of unique checkouts.
877
878 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000879 """
880 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
881 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000882 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000883 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000884 # Skip over File() dependencies as we can't version them.
885 if not isinstance(entry.parsed_url, self.FileImpl):
886 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
887 pprint.pformat(entry.parsed_url))
888 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000889 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000890 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000891 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000892
893 def _ReadEntries(self):
894 """Read the .gclient_entries file for the given client.
895
896 Returns:
897 A sequence of solution names, which will be empty if there is the
898 entries file hasn't been created yet.
899 """
900 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000901 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000902 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000903 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000904 try:
905 exec(gclient_utils.FileRead(filename), scope)
906 except SyntaxError, e:
907 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000908 return scope['entries']
909
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000910 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000911 """Checks for revision overrides."""
912 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000913 if self._options.head:
914 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000915 # Do not check safesync_url if one or more --revision flag is specified.
916 if not self._options.revisions:
917 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000918 if not s.managed:
919 self._options.revisions.append('%s@unmanaged' % s.name)
920 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000921 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000922 if not self._options.revisions:
923 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000924 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000925 index = 0
926 for revision in self._options.revisions:
927 if not '@' in revision:
928 # Support for --revision 123
929 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000930 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000931 if not sol in solutions_names:
932 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
933 print >> sys.stderr, ('Please fix your script, having invalid '
934 '--revision flags will soon considered an error.')
935 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000936 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000937 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000938 return revision_overrides
939
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000940 def _ApplySafeSyncRev(self, dep):
941 """Finds a valid revision from the content of the safesync_url and apply it
942 by appending revisions to the revision list. Throws if revision appears to
943 be invalid for the given |dep|."""
944 assert len(dep.safesync_url) > 0
945 handle = urllib.urlopen(dep.safesync_url)
946 rev = handle.read().strip()
947 handle.close()
948 if not rev:
949 raise gclient_utils.Error(
950 'It appears your safesync_url (%s) is not working properly\n'
951 '(as it returned an empty response). Check your config.' %
952 dep.safesync_url)
953 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
954 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
955 if self._options.verbose:
956 print('Using safesync_url revision: %s.\n' % safe_rev)
957 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
958
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000959 def RunOnDeps(self, command, args):
960 """Runs a command on each dependency in a client and its dependencies.
961
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000962 Args:
963 command: The command to use (e.g., 'status' or 'diff')
964 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000965 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000966 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000967 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000968 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000969 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000970 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000971 if (command in ('update', 'revert') and sys.stdout.isatty() and not
972 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000973 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000974 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000975 for s in self.dependencies:
976 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000977 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000978
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000979 # Once all the dependencies have been processed, it's now safe to run the
980 # hooks.
981 if not self._options.nohooks:
982 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000983
984 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000985 # Notify the user if there is an orphaned entry in their working copy.
986 # Only delete the directory if there are no changes in it, and
987 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000988 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000989 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000990 if not prev_url:
991 # entry must have been overridden via .gclient custom_deps
992 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000993 # Fix path separator on Windows.
994 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000995 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000996 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000997 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000998 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000999 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001000 scm.status(self._options, [], file_list)
1001 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001002 if (not self._options.delete_unversioned_trees or
1003 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001004 # There are modified files in this entry. Keep warning until
1005 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001006 print(('\nWARNING: \'%s\' is no longer part of this client. '
1007 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001008 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001009 else:
1010 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001011 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001012 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001013 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001014 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001015 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001016 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001017
1018 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001019 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001020 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001021 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001022 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001023 for s in self.dependencies:
1024 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001025 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001026
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001027 def GetURLAndRev(dep):
1028 """Returns the revision-qualified SCM url for a Dependency."""
1029 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001030 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001031 if isinstance(dep.parsed_url, self.FileImpl):
1032 original_url = dep.parsed_url.file_location
1033 else:
1034 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001035 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001036 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001037 if not os.path.isdir(scm.checkout_path):
1038 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001039 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001041 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001042 new_gclient = ''
1043 # First level at .gclient
1044 for d in self.dependencies:
1045 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001046 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001047 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001048 for d in dep.dependencies:
1049 entries[d.name] = GetURLAndRev(d)
1050 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001051 GrabDeps(d)
1052 custom_deps = []
1053 for k in sorted(entries.keys()):
1054 if entries[k]:
1055 # Quotes aren't escaped...
1056 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1057 else:
1058 custom_deps.append(' \"%s\": None,\n' % k)
1059 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1060 'solution_name': d.name,
1061 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001062 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001063 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001064 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001065 'solution_deps': ''.join(custom_deps),
1066 }
1067 # Print the snapshot configuration file
1068 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001069 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001070 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001071 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001072 if self._options.actual:
1073 entries[d.name] = GetURLAndRev(d)
1074 else:
1075 entries[d.name] = d.parsed_url
1076 keys = sorted(entries.keys())
1077 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001078 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001079 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001080
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001081 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001082 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001083 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001084
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001085 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001086 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001087 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001088 return self._root_dir
1089
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001090 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001091 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001092 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001093 return self._enforced_os
1094
maruel@chromium.org68988972011-09-20 14:11:42 +00001095 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001096 def recursion_limit(self):
1097 """How recursive can each dependencies in DEPS file can load DEPS file."""
1098 return self._recursion_limit
1099
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001100
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001101#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102
1103
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001104def CMDcleanup(parser, args):
1105 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001106
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001107Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001108"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001109 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1110 help='override deps for the specified (comma-separated) '
1111 'platform(s); \'all\' will process all deps_os '
1112 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001113 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001114 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001115 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001116 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001117 if options.verbose:
1118 # Print out the .gclient file. This is longer than if we just printed the
1119 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001120 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001121 return client.RunOnDeps('cleanup', args)
1122
1123
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001124@attr('usage', '[command] [args ...]')
1125def CMDrecurse(parser, args):
1126 """Operates on all the entries.
1127
1128 Runs a shell command on all entries.
1129 """
1130 # Stop parsing at the first non-arg so that these go through to the command
1131 parser.disable_interspersed_args()
1132 parser.add_option('-s', '--scm', action='append', default=[],
1133 help='choose scm types to operate upon')
maruel@chromium.orgf2ff9482011-10-19 19:45:54 +00001134 parser.remove_option('--jobs')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001135 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001136 if not args:
1137 print >> sys.stderr, 'Need to supply a command!'
1138 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001139 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1140 if not root_and_entries:
1141 print >> sys.stderr, (
1142 'You need to run gclient sync at least once to use \'recurse\'.\n'
1143 'This is because .gclient_entries needs to exist and be up to date.')
1144 return 1
1145 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001146 scm_set = set()
1147 for scm in options.scm:
1148 scm_set.update(scm.split(','))
1149
1150 # Pass in the SCM type as an env variable
1151 env = os.environ.copy()
1152
1153 for path, url in entries.iteritems():
1154 scm = gclient_scm.GetScmName(url)
1155 if scm_set and scm not in scm_set:
1156 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001157 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001158 if scm:
1159 env['GCLIENT_SCM'] = scm
1160 if url:
1161 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001162 if os.path.isdir(cwd):
1163 subprocess2.call(args, cwd=cwd, env=env)
1164 else:
1165 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001166 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001167
1168
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001169@attr('usage', '[args ...]')
1170def CMDfetch(parser, args):
1171 """Fetches upstream commits for all modules.
1172
1173Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1174"""
1175 (_, args) = parser.parse_args(args)
1176 args = ['-s', 'git', 'git', 'fetch'] + args
1177 return CMDrecurse(parser, args)
1178
1179
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001180@attr('usage', '[url] [safesync url]')
1181def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001182 """Create a .gclient file in the current directory.
1183
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001184This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001185top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001186modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001187provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001188URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001189"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001190 parser.add_option('--spec',
1191 help='create a gclient file containing the provided '
1192 'string. Due to Cygwin/Python brokenness, it '
1193 'probably can\'t contain any newlines.')
1194 parser.add_option('--name',
1195 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001196 parser.add_option('--deps-file', default='DEPS',
1197 help='overrides the default name for the DEPS file for the'
1198 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001199 parser.add_option('--unmanaged', action='store_true', default=False,
1200 help='overrides the default behavior to make it possible '
1201 'to have the main solution untouched by gclient '
1202 '(gclient will check out unmanaged dependencies but '
1203 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001204 parser.add_option('--git-deps', action='store_true',
1205 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001206 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001207 if ((options.spec and args) or len(args) > 2 or
1208 (not options.spec and not args)):
1209 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1210
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001211 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001212 if options.spec:
1213 client.SetConfig(options.spec)
1214 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001215 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001216 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001217 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001218 if name.endswith('.git'):
1219 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001220 else:
1221 # specify an alternate relpath for the given URL.
1222 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001223 deps_file = options.deps_file
1224 if options.git_deps:
1225 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001226 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001227 if len(args) > 1:
1228 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001229 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1230 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001231 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001232 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001233
1234
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001235@attr('epilog', """Example:
1236 gclient pack > patch.txt
1237 generate simple patch for configured client and dependences
1238""")
1239def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001240 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001241
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001242Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001243dependencies, and performs minimal postprocessing of the output. The
1244resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001245checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001246"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001247 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1248 help='override deps for the specified (comma-separated) '
1249 'platform(s); \'all\' will process all deps_os '
1250 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001251 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001252 client = GClient.LoadCurrentConfig(options)
1253 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001254 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001255 if options.verbose:
1256 # Print out the .gclient file. This is longer than if we just printed the
1257 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001258 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001259 return client.RunOnDeps('pack', args)
1260
1261
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001262def CMDstatus(parser, args):
1263 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001264 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1265 help='override deps for the specified (comma-separated) '
1266 'platform(s); \'all\' will process all deps_os '
1267 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001268 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001269 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001270 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001271 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001272 if options.verbose:
1273 # Print out the .gclient file. This is longer than if we just printed the
1274 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001275 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001276 return client.RunOnDeps('status', args)
1277
1278
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001279@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001280 gclient sync
1281 update files from SCM according to current configuration,
1282 *for modules which have changed since last update or sync*
1283 gclient sync --force
1284 update files from SCM according to current configuration, for
1285 all modules (useful for recovering files deleted from local copy)
1286 gclient sync --revision src@31000
1287 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001288""")
1289def CMDsync(parser, args):
1290 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001291 parser.add_option('-f', '--force', action='store_true',
1292 help='force update even for unchanged modules')
1293 parser.add_option('-n', '--nohooks', action='store_true',
1294 help='don\'t run hooks after the update is complete')
1295 parser.add_option('-r', '--revision', action='append',
1296 dest='revisions', metavar='REV', default=[],
1297 help='Enforces revision/hash for the solutions with the '
1298 'format src@rev. The src@ part is optional and can be '
1299 'skipped. -r can be used multiple times when .gclient '
1300 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001301 'if the src@ part is skipped. Note that specifying '
1302 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001303 parser.add_option('-t', '--transitive', action='store_true',
1304 help='When a revision is specified (in the DEPS file or '
1305 'with the command-line flag), transitively update '
1306 'the dependencies to the date of the given revision. '
1307 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001308 parser.add_option('-H', '--head', action='store_true',
1309 help='skips any safesync_urls specified in '
1310 'configured solutions and sync to head instead')
1311 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001312 help='Deletes from the working copy any dependencies that '
1313 'have been removed since the last sync, as long as '
1314 'there are no local modifications. When used with '
1315 '--force, such dependencies are removed even if they '
1316 'have local modifications. When used with --reset, '
1317 'all untracked directories are removed from the '
1318 'working copy, exclusing those which are explicitly '
1319 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001320 parser.add_option('-R', '--reset', action='store_true',
1321 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001322 parser.add_option('-M', '--merge', action='store_true',
1323 help='merge upstream changes instead of trying to '
1324 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001325 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1326 help='override deps for the specified (comma-separated) '
1327 'platform(s); \'all\' will process all deps_os '
1328 'references')
1329 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1330 help='Skip svn up whenever possible by requesting '
1331 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001332 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001333 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001334
1335 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001336 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001337
maruel@chromium.org307d1792010-05-31 20:03:13 +00001338 if options.revisions and options.head:
1339 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001340 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001341
1342 if options.verbose:
1343 # Print out the .gclient file. This is longer than if we just printed the
1344 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001345 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001346 return client.RunOnDeps('update', args)
1347
1348
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001349def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001350 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001351 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001352
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001353def CMDdiff(parser, args):
1354 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001355 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1356 help='override deps for the specified (comma-separated) '
1357 'platform(s); \'all\' will process all deps_os '
1358 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001359 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001360 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001361 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001362 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001363 if options.verbose:
1364 # Print out the .gclient file. This is longer than if we just printed the
1365 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001366 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001367 return client.RunOnDeps('diff', args)
1368
1369
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001370def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001371 """Revert all modifications in every dependencies.
1372
1373 That's the nuclear option to get back to a 'clean' state. It removes anything
1374 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001375 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1376 help='override deps for the specified (comma-separated) '
1377 'platform(s); \'all\' will process all deps_os '
1378 'references')
1379 parser.add_option('-n', '--nohooks', action='store_true',
1380 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001381 (options, args) = parser.parse_args(args)
1382 # --force is implied.
1383 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001384 options.reset = False
1385 options.delete_unversioned_trees = False
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 return client.RunOnDeps('revert', args)
1390
1391
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001392def CMDrunhooks(parser, args):
1393 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001394 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1395 help='override deps for the specified (comma-separated) '
1396 'platform(s); \'all\' will process all deps_os '
1397 'references')
1398 parser.add_option('-f', '--force', action='store_true', default=True,
1399 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001400 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001401 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001402 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001403 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001404 if options.verbose:
1405 # Print out the .gclient file. This is longer than if we just printed the
1406 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001407 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001408 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001409 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001410 return client.RunOnDeps('runhooks', args)
1411
1412
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001413def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001414 """Output revision info mapping for the client and its dependencies.
1415
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001416 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001417 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001418 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1419 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001420 commit can change.
1421 """
1422 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1423 help='override deps for the specified (comma-separated) '
1424 'platform(s); \'all\' will process all deps_os '
1425 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001426 parser.add_option('-a', '--actual', action='store_true',
1427 help='gets the actual checked out revisions instead of the '
1428 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001429 parser.add_option('-s', '--snapshot', action='store_true',
1430 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001431 'version of all repositories to reproduce the tree, '
1432 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001433 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001434 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001435 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001436 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001437 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001438 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001439
1440
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001441def Command(name):
1442 return getattr(sys.modules[__name__], 'CMD' + name, None)
1443
1444
1445def CMDhelp(parser, args):
1446 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001447 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001448 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001449 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001450 parser.print_help()
1451 return 0
1452
1453
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001454def GenUsage(parser, command):
1455 """Modify an OptParse object with the function's documentation."""
1456 obj = Command(command)
1457 if command == 'help':
1458 command = '<command>'
1459 # OptParser.description prefer nicely non-formatted strings.
1460 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1461 usage = getattr(obj, 'usage', '')
1462 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1463 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001464
1465
maruel@chromium.org0895b752011-08-26 20:40:33 +00001466def Parser():
1467 """Returns the default parser."""
1468 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org41071612011-10-19 19:58:08 +00001469 # cygwin has issues with parallel sync
1470 jobs = 1 if sys.platform == 'cygwin' else 8
1471 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001472 help='Specify how many SCM commands can run in parallel; '
1473 'default=%default')
1474 parser.add_option('-v', '--verbose', action='count', default=0,
1475 help='Produces additional output for diagnostics. Can be '
1476 'used up to three times for more logging info.')
1477 parser.add_option('--gclientfile', dest='config_filename',
1478 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1479 help='Specify an alternate %default file')
1480 # Integrate standard options processing.
1481 old_parser = parser.parse_args
1482 def Parse(args):
1483 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001484 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1485 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001486 logging.basicConfig(level=level,
1487 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1488 options.entries_filename = options.config_filename + '_entries'
1489 if options.jobs < 1:
1490 parser.error('--jobs must be 1 or higher')
1491
1492 # These hacks need to die.
1493 if not hasattr(options, 'revisions'):
1494 # GClient.RunOnDeps expects it even if not applicable.
1495 options.revisions = []
1496 if not hasattr(options, 'head'):
1497 options.head = None
1498 if not hasattr(options, 'nohooks'):
1499 options.nohooks = True
1500 if not hasattr(options, 'deps_os'):
1501 options.deps_os = None
1502 if not hasattr(options, 'manually_grab_svn_rev'):
1503 options.manually_grab_svn_rev = None
1504 if not hasattr(options, 'force'):
1505 options.force = None
1506 return (options, args)
1507 parser.parse_args = Parse
1508 # We don't want wordwrapping in epilog (usually examples)
1509 parser.format_epilog = lambda _: parser.epilog or ''
1510 return parser
1511
1512
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001513def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001514 """Doesn't parse the arguments here, just find the right subcommand to
1515 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001516 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001517 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001518 '\nYour python version %s is unsupported, please upgrade.\n' %
1519 sys.version.split(' ', 1)[0])
1520 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001521 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001522 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001523 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1524 # operations. Python as a strong tendency to buffer sys.stdout.
1525 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001526 # Make stdout annotated with the thread ids.
1527 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001528 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001529 # Unused variable 'usage'
1530 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001531 def to_str(fn):
1532 return (
1533 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1534 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1535 cmds = (
1536 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1537 )
1538 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001539 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001540 if argv:
1541 command = Command(argv[0])
1542 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001543 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001544 GenUsage(parser, argv[0])
1545 return command(parser, argv[1:])
1546 # Not a known command. Default to help.
1547 GenUsage(parser, 'help')
1548 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001549 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001550 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001551 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001552
1553
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001554if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001555 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001556 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001557
1558# vim: ts=2:sw=2:tw=80:et: