blob: 0ebd1e9b720a73b893e2b9f57015afe159436805 [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
bradnelson@google.com4949dab2012-04-19 16:41:07 +000058import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000059import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000060import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000064import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000065
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000066import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000067
maruel@chromium.org35625c72011-03-23 17:34:02 +000068import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000069import gclient_scm
70import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000071from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000072import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +000073from third_party import colorama
74# Import shortcut.
75from third_party.colorama import Fore
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000078def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000079 """Sets an attribute on a function."""
80 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000081 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000082 return fn
83 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000084
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000086## GClient implementation.
87
88
maruel@chromium.org116704f2010-06-11 17:34:38 +000089class GClientKeywords(object):
90 class FromImpl(object):
91 """Used to implement the From() syntax."""
92
93 def __init__(self, module_name, sub_target_name=None):
94 """module_name is the dep module we want to include from. It can also be
95 the name of a subdirectory to include from.
96
97 sub_target_name is an optional parameter if the module name in the other
98 DEPS file is different. E.g., you might want to map src/net to net."""
99 self.module_name = module_name
100 self.sub_target_name = sub_target_name
101
102 def __str__(self):
103 return 'From(%s, %s)' % (repr(self.module_name),
104 repr(self.sub_target_name))
105
maruel@chromium.org116704f2010-06-11 17:34:38 +0000106 class FileImpl(object):
107 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000108 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000109
110 def __init__(self, file_location):
111 self.file_location = file_location
112
113 def __str__(self):
114 return 'File("%s")' % self.file_location
115
116 def GetPath(self):
117 return os.path.split(self.file_location)[0]
118
119 def GetFilename(self):
120 rev_tokens = self.file_location.split('@')
121 return os.path.split(rev_tokens[0])[1]
122
123 def GetRevision(self):
124 rev_tokens = self.file_location.split('@')
125 if len(rev_tokens) > 1:
126 return rev_tokens[1]
127 return None
128
129 class VarImpl(object):
130 def __init__(self, custom_vars, local_scope):
131 self._custom_vars = custom_vars
132 self._local_scope = local_scope
133
134 def Lookup(self, var_name):
135 """Implements the Var syntax."""
136 if var_name in self._custom_vars:
137 return self._custom_vars[var_name]
138 elif var_name in self._local_scope.get("vars", {}):
139 return self._local_scope["vars"][var_name]
140 raise gclient_utils.Error("Var is not defined: %s" % var_name)
141
142
maruel@chromium.org064186c2011-09-27 23:53:33 +0000143class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000144 """Immutable configuration settings."""
145 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000146 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000147 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000148 GClientKeywords.__init__(self)
149
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000150 # These are not mutable:
151 self._parent = parent
152 self._safesync_url = safesync_url
153 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000154 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000155 # 'managed' determines whether or not this dependency is synced/updated by
156 # gclient after gclient checks it out initially. The difference between
157 # 'managed' and 'should_process' is that the user specifies 'managed' via
158 # the --unmanaged command-line flag or a .gclient config, where
159 # 'should_process' is dynamically set by gclient if it goes over its
160 # recursion limit and controls gclient's behavior so it does not misbehave.
161 self._managed = managed
162 self._should_process = should_process
163
164 # These are only set in .gclient and not in DEPS files.
165 self._custom_vars = custom_vars or {}
166 self._custom_deps = custom_deps or {}
167
maruel@chromium.org064186c2011-09-27 23:53:33 +0000168 # Post process the url to remove trailing slashes.
169 if isinstance(self._url, basestring):
170 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
171 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000172 self._url = self._url.replace('/@', '@')
173 elif not isinstance(self._url,
174 (self.FromImpl, self.FileImpl, None.__class__)):
175 raise gclient_utils.Error(
176 ('dependency url must be either a string, None, '
177 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000178 if '/' in self._deps_file or '\\' in self._deps_file:
179 raise gclient_utils.Error('deps_file name must not be a path, just a '
180 'filename. %s' % self._deps_file)
181
182 @property
183 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000184 return self._deps_file
185
186 @property
187 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000188 return self._managed
189
190 @property
191 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000192 return self._parent
193
194 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000195 def root(self):
196 """Returns the root node, a GClient object."""
197 if not self.parent:
198 # This line is to signal pylint that it could be a GClient instance.
199 return self or GClient(None, None)
200 return self.parent.root
201
202 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000203 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000204 return self._safesync_url
205
206 @property
207 def should_process(self):
208 """True if this dependency should be processed, i.e. checked out."""
209 return self._should_process
210
211 @property
212 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000213 return self._custom_vars.copy()
214
215 @property
216 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000217 return self._custom_deps.copy()
218
maruel@chromium.org064186c2011-09-27 23:53:33 +0000219 @property
220 def url(self):
221 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000222
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000223 @property
224 def recursion_limit(self):
225 """Returns > 0 if this dependency is not too recursed to be processed."""
226 return max(self.parent.recursion_limit - 1, 0)
227
228 def get_custom_deps(self, name, url):
229 """Returns a custom deps if applicable."""
230 if self.parent:
231 url = self.parent.get_custom_deps(name, url)
232 # None is a valid return value to disable a dependency.
233 return self.custom_deps.get(name, url)
234
maruel@chromium.org064186c2011-09-27 23:53:33 +0000235
236class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000237 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000238
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000239 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000240 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000241 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000243 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000245
246 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000247 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000248
249 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000250 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000251 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000252 # A cache of the files affected by the current operation, necessary for
253 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000254 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000255 # If it is not set to True, the dependency wasn't processed for its child
256 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000258 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000260 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000261 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000262
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000263 if not self.name and self.parent:
264 raise gclient_utils.Error('Dependency without name')
265
maruel@chromium.org470b5432011-10-11 18:18:19 +0000266 @property
267 def requirements(self):
268 """Calculate the list of requirements."""
269 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000270 # self.parent is implicitly a requirement. This will be recursive by
271 # definition.
272 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000273 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000274
275 # For a tree with at least 2 levels*, the leaf node needs to depend
276 # on the level higher up in an orderly way.
277 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
278 # thus unsorted, while the .gclient format is a list thus sorted.
279 #
280 # * _recursion_limit is hard coded 2 and there is no hope to change this
281 # value.
282 #
283 # Interestingly enough, the following condition only works in the case we
284 # want: self is a 2nd level node. 3nd level node wouldn't need this since
285 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000286 if self.parent and self.parent.parent and not self.parent.parent.parent:
287 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000288
289 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000290 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000291
maruel@chromium.org470b5432011-10-11 18:18:19 +0000292 if self.name:
293 requirements |= set(
294 obj.name for obj in self.root.subtree(False)
295 if (obj is not self
296 and obj.name and
297 self.name.startswith(posixpath.join(obj.name, ''))))
298 requirements = tuple(sorted(requirements))
299 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
300 return requirements
301
302 def verify_validity(self):
303 """Verifies that this Dependency is fine to add as a child of another one.
304
305 Returns True if this entry should be added, False if it is a duplicate of
306 another entry.
307 """
308 logging.info('Dependency(%s).verify_validity()' % self.name)
309 if self.name in [s.name for s in self.parent.dependencies]:
310 raise gclient_utils.Error(
311 'The same name "%s" appears multiple times in the deps section' %
312 self.name)
313 if not self.should_process:
314 # Return early, no need to set requirements.
315 return True
316
317 # This require a full tree traversal with locks.
318 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
319 for sibling in siblings:
320 if self.url != sibling.url:
321 raise gclient_utils.Error(
322 'Dependency %s specified more than once:\n %s\nvs\n %s' %
323 (self.name, sibling.hierarchy(), self.hierarchy()))
324 # In theory we could keep it as a shadow of the other one. In
325 # practice, simply ignore it.
326 logging.warn('Won\'t process duplicate dependency %s' % sibling)
327 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000328 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000329
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000330 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000331 """Resolves the parsed url from url.
332
333 Manages From() keyword accordingly. Do not touch self.parsed_url nor
334 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000335 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000336 parsed_url = self.get_custom_deps(self.name, url)
337 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000338 logging.info(
339 'Dependency(%s).LateOverride(%s) -> %s' %
340 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000341 return parsed_url
342
343 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000344 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000345 ref = [
346 dep for dep in self.root.subtree(True) if url.module_name == dep.name
347 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000348 if not ref:
349 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
350 url.module_name, ref))
351 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000352 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000353 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000354 found_deps = [d for d in ref.dependencies if d.name == sub_target]
355 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000356 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000357 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
358 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000359 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000360
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000361 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000362 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000363 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000364 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000365 'Dependency(%s).LateOverride(%s) -> %s (From)' %
366 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000367 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000368
369 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000370 parsed_url = urlparse.urlparse(url)
371 if not parsed_url[0]:
372 # A relative url. Fetch the real base.
373 path = parsed_url[2]
374 if not path.startswith('/'):
375 raise gclient_utils.Error(
376 'relative DEPS entry \'%s\' must begin with a slash' % url)
377 # Create a scm just to query the full url.
378 parent_url = self.parent.parsed_url
379 if isinstance(parent_url, self.FileImpl):
380 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000381 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000382 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000383 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000384 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000385 logging.info(
386 'Dependency(%s).LateOverride(%s) -> %s' %
387 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000388 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000389
390 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000391 logging.info(
392 'Dependency(%s).LateOverride(%s) -> %s (File)' %
393 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000394 return url
395
396 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000397 logging.info(
398 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000399 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000400
401 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000402
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000403 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000404 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000405 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000406 assert not self.dependencies
407 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000408 local_scope = {}
409 var = self.VarImpl(self.custom_vars, local_scope)
410 global_scope = {
411 'File': self.FileImpl,
412 'From': self.FromImpl,
413 'Var': var.Lookup,
414 'deps_os': {},
415 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000416 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000417 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000418 logging.info(
419 'ParseDepsFile(%s): No %s file found at %s' % (
420 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000421 else:
422 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000423 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000424 # Eval the content.
425 try:
426 exec(deps_content, global_scope, local_scope)
427 except SyntaxError, e:
428 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000429 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000430 # load os specific dependencies if defined. these dependencies may
431 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000432 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000433 enforced_os = self.root.enforced_os
434 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000435 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000436 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000437 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000438 # platform, so we collect the broadest set of dependencies
439 # available. We may end up with the wrong revision of something for
440 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000441 deps.update([x for x in os_deps.items() if not x[0] in deps])
442 else:
443 deps.update(os_deps)
444
maruel@chromium.org271375b2010-06-23 19:17:38 +0000445 # If a line is in custom_deps, but not in the solution, we want to append
446 # this line to the solution.
447 for d in self.custom_deps:
448 if d not in deps:
449 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000450
451 # If use_relative_paths is set in the DEPS file, regenerate
452 # the dictionary using paths relative to the directory containing
453 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000454 use_relative_paths = local_scope.get('use_relative_paths', False)
455 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000456 rel_deps = {}
457 for d, url in deps.items():
458 # normpath is required to allow DEPS to use .. in their
459 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000460 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
461 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000462
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000463 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000464 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000465 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000466 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000467 deps_to_add.append(Dependency(
468 self, name, url, None, None, None, None,
469 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000470 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000471 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
472 logging.info('ParseDepsFile(%s) done' % self.name)
473
474 def add_dependencies_and_close(self, deps_to_add, hooks):
475 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000476 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000477 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000478 self.add_dependency(dep)
479 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000480
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000481 @staticmethod
482 def maybeGetParentRevision(
483 command, options, parsed_url, parent_name, revision_overrides):
484 """If we are performing an update and --transitive is set, set the
485 revision to the parent's revision. If we have an explicit revision
486 do nothing."""
487 if command == 'update' and options.transitive and not options.revision:
488 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
489 if not revision:
490 options.revision = revision_overrides.get(parent_name)
491 if options.verbose and options.revision:
492 print("Using parent's revision date: %s" % options.revision)
493 # If the parent has a revision override, then it must have been
494 # converted to date format.
495 assert (not options.revision or
496 gclient_utils.IsDateRevision(options.revision))
497
498 @staticmethod
499 def maybeConvertToDateRevision(
500 command, options, name, scm, revision_overrides):
501 """If we are performing an update and --transitive is set, convert the
502 revision to a date-revision (if necessary). Instead of having
503 -r 101 replace the revision with the time stamp of 101 (e.g.
504 "{2011-18-04}").
505 This way dependencies are upgraded to the revision they had at the
506 check-in of revision 101."""
507 if (command == 'update' and
508 options.transitive and
509 options.revision and
510 not gclient_utils.IsDateRevision(options.revision)):
511 revision_date = scm.GetRevisionDate(options.revision)
512 revision = gclient_utils.MakeDateRevision(revision_date)
513 if options.verbose:
514 print("Updating revision override from %s to %s." %
515 (options.revision, revision))
516 revision_overrides[name] = revision
517
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000518 # Arguments number differs from overridden method
519 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000520 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000521 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000522 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000523 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000524 if not self.should_process:
525 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000526 # When running runhooks, there's no need to consult the SCM.
527 # All known hooks are expected to run unconditionally regardless of working
528 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000529 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000530 parsed_url = self.LateOverride(self.url)
531 file_list = []
532 if run_scm and parsed_url:
533 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000534 # Special support for single-file checkout.
535 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000536 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
537 # pylint: disable=E1103
538 options.revision = parsed_url.GetRevision()
539 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000540 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000541 self.name)
542 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000543 args + [parsed_url.GetFilename()],
544 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000545 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000546 # Create a shallow copy to mutate revision.
547 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000548 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000549 self.maybeGetParentRevision(
550 command, options, parsed_url, self.parent.name, revision_overrides)
551 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
552 scm.RunCommand(command, options, args, file_list)
553 self.maybeConvertToDateRevision(
554 command, options, self.name, scm, revision_overrides)
555 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000556
557 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
558 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000559 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000560 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000561 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000562 continue
563 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000564 [self.root.root_dir.lower(), file_list[i].lower()])
565 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000566 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000567 while file_list[i].startswith(('\\', '/')):
568 file_list[i] = file_list[i][1:]
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000569 elif command is 'recurse':
570 if not isinstance(parsed_url, self.FileImpl):
571 # Skip file only checkout.
572 scm = gclient_scm.GetScmName(parsed_url)
573 if not options.scm or scm in options.scm:
574 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
575 # Pass in the SCM type as an env variable
576 env = os.environ.copy()
577 if scm:
578 env['GCLIENT_SCM'] = scm
579 if parsed_url:
580 env['GCLIENT_URL'] = parsed_url
581 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000582 try:
583 gclient_utils.CheckCallAndFilter(
584 args, cwd=cwd, env=env, print_stdout=True)
585 except subprocess2.CalledProcessError:
586 if not options.ignore:
587 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000588 else:
589 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000590
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000591 # Always parse the DEPS file.
592 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000593
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000594 self._run_is_done(file_list, parsed_url)
595
596 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000597 # Parse the dependencies of this dependency.
598 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000599 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000600
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000601 @gclient_utils.lockedmethod
602 def _run_is_done(self, file_list, parsed_url):
603 # Both these are kept for hooks that are run as a separate tree traversal.
604 self._file_list = file_list
605 self._parsed_url = parsed_url
606 self._processed = True
607
szager@google.comb9a78d32012-03-13 18:46:21 +0000608 @staticmethod
609 def GetHookAction(hook_dict, matching_file_list):
610 """Turns a parsed 'hook' dict into an executable command."""
611 logging.debug(hook_dict)
612 logging.debug(matching_file_list)
613 command = hook_dict['action'][:]
614 if command[0] == 'python':
615 # If the hook specified "python" as the first item, the action is a
616 # Python script. Run it by starting a new copy of the same
617 # interpreter.
618 command[0] = sys.executable
619 if '$matching_files' in command:
620 splice_index = command.index('$matching_files')
621 command[splice_index:splice_index + 1] = matching_file_list
622 return command
623
624 def GetHooks(self, options):
625 """Evaluates all hooks, and return them in a flat list.
626
627 RunOnDeps() must have been called before to load the DEPS.
628 """
629 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000630 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000631 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000632 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000633 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000634 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000635 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000636 # TODO(maruel): If the user is using git or git-svn, then we don't know
637 # what files have changed so we always run all hooks. It'd be nice to fix
638 # that.
639 if (options.force or
640 isinstance(self.parsed_url, self.FileImpl) or
641 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000642 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000643 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000644 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000645 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000646 # Run hooks on the basis of whether the files from the gclient operation
647 # match each hook's pattern.
648 for hook_dict in self.deps_hooks:
649 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000650 matching_file_list = [
651 f for f in self.file_list_and_children if pattern.search(f)
652 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000653 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000654 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000655 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000656 result.extend(s.GetHooks(options))
657 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000658
szager@google.comb9a78d32012-03-13 18:46:21 +0000659 def RunHooksRecursively(self, options):
660 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000661 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000662 for hook in self.GetHooks(options):
663 try:
664 gclient_utils.CheckCallAndFilterAndHeader(
665 hook, cwd=self.root.root_dir, always=True)
666 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
667 # Use a discrete exit status code of 2 to indicate that a hook action
668 # failed. Users of this script may wish to treat hook action failures
669 # differently from VC failures.
670 print >> sys.stderr, 'Error: %s' % str(e)
671 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000672
maruel@chromium.org0d812442010-08-10 12:41:08 +0000673 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000674 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000675 dependencies = self.dependencies
676 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000677 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000678 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000679 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000680 for i in d.subtree(include_all):
681 yield i
682
683 def depth_first_tree(self):
684 """Depth-first recursion including the root node."""
685 yield self
686 for i in self.dependencies:
687 for j in i.depth_first_tree():
688 if j.should_process:
689 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000690
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000691 @gclient_utils.lockedmethod
692 def add_dependency(self, new_dep):
693 self._dependencies.append(new_dep)
694
695 @gclient_utils.lockedmethod
696 def _mark_as_parsed(self, new_hooks):
697 self._deps_hooks.extend(new_hooks)
698 self._deps_parsed = True
699
maruel@chromium.org68988972011-09-20 14:11:42 +0000700 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000701 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000702 def dependencies(self):
703 return tuple(self._dependencies)
704
705 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000706 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000707 def deps_hooks(self):
708 return tuple(self._deps_hooks)
709
710 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000711 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000712 def parsed_url(self):
713 return self._parsed_url
714
715 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000716 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000717 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000718 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000719 return self._deps_parsed
720
721 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000722 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000723 def processed(self):
724 return self._processed
725
726 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000727 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000728 def hooks_ran(self):
729 return self._hooks_ran
730
731 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000732 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000733 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000734 return tuple(self._file_list)
735
736 @property
737 def file_list_and_children(self):
738 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000739 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000740 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000741 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000742
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000743 def __str__(self):
744 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000745 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000746 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000747 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000748 # First try the native property if it exists.
749 if hasattr(self, '_' + i):
750 value = getattr(self, '_' + i, False)
751 else:
752 value = getattr(self, i, False)
753 if value:
754 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000755
756 for d in self.dependencies:
757 out.extend([' ' + x for x in str(d).splitlines()])
758 out.append('')
759 return '\n'.join(out)
760
761 def __repr__(self):
762 return '%s: %s' % (self.name, self.url)
763
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000764 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000765 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000766 out = '%s(%s)' % (self.name, self.url)
767 i = self.parent
768 while i and i.name:
769 out = '%s(%s) -> %s' % (i.name, i.url, out)
770 i = i.parent
771 return out
772
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000773
774class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000775 """Object that represent a gclient checkout. A tree of Dependency(), one per
776 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000777
778 DEPS_OS_CHOICES = {
779 "win32": "win",
780 "win": "win",
781 "cygwin": "win",
782 "darwin": "mac",
783 "mac": "mac",
784 "unix": "unix",
785 "linux": "unix",
786 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000787 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000788 }
789
790 DEFAULT_CLIENT_FILE_TEXT = ("""\
791solutions = [
792 { "name" : "%(solution_name)s",
793 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000794 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000795 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000796 "custom_deps" : {
797 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000798 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000799 },
800]
801""")
802
803 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
804 { "name" : "%(solution_name)s",
805 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000806 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000807 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000808 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000809%(solution_deps)s },
810 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000811 },
812""")
813
814 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
815# Snapshot generated with gclient revinfo --snapshot
816solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000817%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000818""")
819
820 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000821 # Do not change previous behavior. Only solution level and immediate DEPS
822 # are processed.
823 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000824 Dependency.__init__(self, None, None, None, None, True, None, None,
825 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000826 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000827 if options.deps_os:
828 enforced_os = options.deps_os.split(',')
829 else:
830 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
831 if 'all' in enforced_os:
832 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000833 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000834 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000835 self.config_content = None
836
837 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000838 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000839 config_dict = {}
840 self.config_content = content
841 try:
842 exec(content, config_dict)
843 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000844 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000845
846 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000847 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000848 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000849 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000850 self, s['name'], s['url'],
851 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000852 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000853 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000854 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000855 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000856 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000857 except KeyError:
858 raise gclient_utils.Error('Invalid .gclient file. Solution is '
859 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000860 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
861 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000862
863 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000864 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000865 self._options.config_filename),
866 self.config_content)
867
868 @staticmethod
869 def LoadCurrentConfig(options):
870 """Searches for and loads a .gclient file relative to the current working
871 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000872 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000873 if not path:
874 return None
875 client = GClient(path, options)
876 client.SetConfig(gclient_utils.FileRead(
877 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000878
879 if (options.revisions and
880 len(client.dependencies) > 1 and
881 any('@' not in r for r in options.revisions)):
882 print >> sys.stderr, (
883 'You must specify the full solution name like --revision %s@%s\n'
884 'when you have multiple solutions setup in your .gclient file.\n'
885 'Other solutions present are: %s.') % (
886 client.dependencies[0].name,
887 options.revisions[0],
888 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000889 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000890
nsylvain@google.comefc80932011-05-31 21:27:56 +0000891 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000892 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000893 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
894 'solution_name': solution_name,
895 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000896 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000897 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000898 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000899 })
900
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000901 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000902 """Creates a .gclient_entries file to record the list of unique checkouts.
903
904 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000905 """
906 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
907 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000908 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000909 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000910 # Skip over File() dependencies as we can't version them.
911 if not isinstance(entry.parsed_url, self.FileImpl):
912 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
913 pprint.pformat(entry.parsed_url))
914 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000915 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000916 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000917 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000918
919 def _ReadEntries(self):
920 """Read the .gclient_entries file for the given client.
921
922 Returns:
923 A sequence of solution names, which will be empty if there is the
924 entries file hasn't been created yet.
925 """
926 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000927 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000928 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000929 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000930 try:
931 exec(gclient_utils.FileRead(filename), scope)
932 except SyntaxError, e:
933 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000934 return scope['entries']
935
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000936 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000937 """Checks for revision overrides."""
938 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000939 if self._options.head:
940 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000941 # Do not check safesync_url if one or more --revision flag is specified.
942 if not self._options.revisions:
943 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000944 if not s.managed:
945 self._options.revisions.append('%s@unmanaged' % s.name)
946 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000947 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000948 if not self._options.revisions:
949 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000950 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000951 index = 0
952 for revision in self._options.revisions:
953 if not '@' in revision:
954 # Support for --revision 123
955 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000956 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000957 if not sol in solutions_names:
958 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
959 print >> sys.stderr, ('Please fix your script, having invalid '
960 '--revision flags will soon considered an error.')
961 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000962 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000963 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000964 return revision_overrides
965
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000966 def _ApplySafeSyncRev(self, dep):
967 """Finds a valid revision from the content of the safesync_url and apply it
968 by appending revisions to the revision list. Throws if revision appears to
969 be invalid for the given |dep|."""
970 assert len(dep.safesync_url) > 0
971 handle = urllib.urlopen(dep.safesync_url)
972 rev = handle.read().strip()
973 handle.close()
974 if not rev:
975 raise gclient_utils.Error(
976 'It appears your safesync_url (%s) is not working properly\n'
977 '(as it returned an empty response). Check your config.' %
978 dep.safesync_url)
979 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
980 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
981 if self._options.verbose:
982 print('Using safesync_url revision: %s.\n' % safe_rev)
983 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
984
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000985 def RunOnDeps(self, command, args):
986 """Runs a command on each dependency in a client and its dependencies.
987
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988 Args:
989 command: The command to use (e.g., 'status' or 'diff')
990 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000991 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000992 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000993 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000994 revision_overrides = {}
995 # It's unnecessary to check for revision overrides for 'recurse'.
996 # Save a few seconds by not calling _EnforceRevisions() in that case.
997 if command is not 'recurse':
998 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000999 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001000 # Disable progress for non-tty stdout.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001001 if (sys.stdout.isatty() and not self._options.verbose):
1002 if command in ('update', 'revert'):
1003 pm = Progress('Syncing projects', 1)
1004 elif command is 'recurse':
1005 pm = Progress(' '.join(args), 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001006 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001007 for s in self.dependencies:
1008 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001009 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001010
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001011 # Once all the dependencies have been processed, it's now safe to run the
1012 # hooks.
1013 if not self._options.nohooks:
1014 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001015
1016 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001017 # Notify the user if there is an orphaned entry in their working copy.
1018 # Only delete the directory if there are no changes in it, and
1019 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001020 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001021 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001022 if not prev_url:
1023 # entry must have been overridden via .gclient custom_deps
1024 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001025 # Fix path separator on Windows.
1026 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001027 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001028 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001029 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001030 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001031 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001032 scm.status(self._options, [], file_list)
1033 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001034 if (not self._options.delete_unversioned_trees or
1035 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001036 # There are modified files in this entry. Keep warning until
1037 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001038 print(('\nWARNING: \'%s\' is no longer part of this client. '
1039 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001040 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001041 else:
1042 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001043 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001044 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001045 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001046 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001047 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001048 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049
1050 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001051 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001052 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001053 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001054 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001055 for s in self.dependencies:
1056 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001057 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001058
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001059 def GetURLAndRev(dep):
1060 """Returns the revision-qualified SCM url for a Dependency."""
1061 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001062 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001063 if isinstance(dep.parsed_url, self.FileImpl):
1064 original_url = dep.parsed_url.file_location
1065 else:
1066 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001067 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001068 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001069 if not os.path.isdir(scm.checkout_path):
1070 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001071 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001072
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001073 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001074 new_gclient = ''
1075 # First level at .gclient
1076 for d in self.dependencies:
1077 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001078 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001079 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001080 for d in dep.dependencies:
1081 entries[d.name] = GetURLAndRev(d)
1082 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001083 GrabDeps(d)
1084 custom_deps = []
1085 for k in sorted(entries.keys()):
1086 if entries[k]:
1087 # Quotes aren't escaped...
1088 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1089 else:
1090 custom_deps.append(' \"%s\": None,\n' % k)
1091 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1092 'solution_name': d.name,
1093 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001094 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001095 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001096 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001097 'solution_deps': ''.join(custom_deps),
1098 }
1099 # Print the snapshot configuration file
1100 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001101 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001102 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001103 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001104 if self._options.actual:
1105 entries[d.name] = GetURLAndRev(d)
1106 else:
1107 entries[d.name] = d.parsed_url
1108 keys = sorted(entries.keys())
1109 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001110 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001111 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001112
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001113 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001114 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001115 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001116
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001117 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001118 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001119 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001120 return self._root_dir
1121
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001122 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001123 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001124 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001125 return self._enforced_os
1126
maruel@chromium.org68988972011-09-20 14:11:42 +00001127 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001128 def recursion_limit(self):
1129 """How recursive can each dependencies in DEPS file can load DEPS file."""
1130 return self._recursion_limit
1131
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001132
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001133#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001134
1135
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001136def CMDcleanup(parser, args):
1137 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001138
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001139Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001140"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001141 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1142 help='override deps for the specified (comma-separated) '
1143 'platform(s); \'all\' will process all deps_os '
1144 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001145 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001146 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001147 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001148 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001149 if options.verbose:
1150 # Print out the .gclient file. This is longer than if we just printed the
1151 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001152 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001153 return client.RunOnDeps('cleanup', args)
1154
1155
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001156@attr('usage', '[command] [args ...]')
1157def CMDrecurse(parser, args):
1158 """Operates on all the entries.
1159
1160 Runs a shell command on all entries.
1161 """
1162 # Stop parsing at the first non-arg so that these go through to the command
1163 parser.disable_interspersed_args()
1164 parser.add_option('-s', '--scm', action='append', default=[],
1165 help='choose scm types to operate upon')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001166 parser.add_option('-i', '--ignore', action='store_true',
1167 help='continue processing in case of non zero return code')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001168 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001169 if not args:
1170 print >> sys.stderr, 'Need to supply a command!'
1171 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001172 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1173 if not root_and_entries:
1174 print >> sys.stderr, (
1175 'You need to run gclient sync at least once to use \'recurse\'.\n'
1176 'This is because .gclient_entries needs to exist and be up to date.')
1177 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001178
1179 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001180 scm_set = set()
1181 for scm in options.scm:
1182 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001183 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001184
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001185 options.nohooks = True
1186 client = GClient.LoadCurrentConfig(options)
1187 return client.RunOnDeps('recurse', args)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001188
1189
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001190@attr('usage', '[args ...]')
1191def CMDfetch(parser, args):
1192 """Fetches upstream commits for all modules.
1193
1194Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1195"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001196 (options, args) = parser.parse_args(args)
1197 args = ['-j%d' % options.jobs, '-s', 'git', 'git', 'fetch'] + args
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001198 return CMDrecurse(parser, args)
1199
1200
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001201@attr('usage', '[url] [safesync url]')
1202def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001203 """Create a .gclient file in the current directory.
1204
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001205This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001206top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001207modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001208provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001209URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001210"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001211 parser.add_option('--spec',
1212 help='create a gclient file containing the provided '
1213 'string. Due to Cygwin/Python brokenness, it '
1214 'probably can\'t contain any newlines.')
1215 parser.add_option('--name',
1216 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001217 parser.add_option('--deps-file', default='DEPS',
1218 help='overrides the default name for the DEPS file for the'
1219 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001220 parser.add_option('--unmanaged', action='store_true', default=False,
1221 help='overrides the default behavior to make it possible '
1222 'to have the main solution untouched by gclient '
1223 '(gclient will check out unmanaged dependencies but '
1224 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001225 parser.add_option('--git-deps', action='store_true',
1226 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001227 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001228 if ((options.spec and args) or len(args) > 2 or
1229 (not options.spec and not args)):
1230 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1231
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001232 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001233 if options.spec:
1234 client.SetConfig(options.spec)
1235 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001236 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001237 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001238 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001239 if name.endswith('.git'):
1240 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001241 else:
1242 # specify an alternate relpath for the given URL.
1243 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001244 deps_file = options.deps_file
1245 if options.git_deps:
1246 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001247 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001248 if len(args) > 1:
1249 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001250 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1251 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001252 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001253 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001254
1255
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001256@attr('epilog', """Example:
1257 gclient pack > patch.txt
1258 generate simple patch for configured client and dependences
1259""")
1260def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001261 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001262
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001263Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001264dependencies, and performs minimal postprocessing of the output. The
1265resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001266checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001267"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001268 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1269 help='override deps for the specified (comma-separated) '
1270 'platform(s); \'all\' will process all deps_os '
1271 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001272 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001273 client = GClient.LoadCurrentConfig(options)
1274 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001275 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001276 if options.verbose:
1277 # Print out the .gclient file. This is longer than if we just printed the
1278 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001279 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001280 return client.RunOnDeps('pack', args)
1281
1282
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001283def CMDstatus(parser, args):
1284 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001285 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1286 help='override deps for the specified (comma-separated) '
1287 'platform(s); \'all\' will process all deps_os '
1288 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001289 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001290 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001291 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001292 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001293 if options.verbose:
1294 # Print out the .gclient file. This is longer than if we just printed the
1295 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001296 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001297 return client.RunOnDeps('status', args)
1298
1299
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001300@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001301 gclient sync
1302 update files from SCM according to current configuration,
1303 *for modules which have changed since last update or sync*
1304 gclient sync --force
1305 update files from SCM according to current configuration, for
1306 all modules (useful for recovering files deleted from local copy)
1307 gclient sync --revision src@31000
1308 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001309""")
1310def CMDsync(parser, args):
1311 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001312 parser.add_option('-f', '--force', action='store_true',
1313 help='force update even for unchanged modules')
1314 parser.add_option('-n', '--nohooks', action='store_true',
1315 help='don\'t run hooks after the update is complete')
1316 parser.add_option('-r', '--revision', action='append',
1317 dest='revisions', metavar='REV', default=[],
1318 help='Enforces revision/hash for the solutions with the '
1319 'format src@rev. The src@ part is optional and can be '
1320 'skipped. -r can be used multiple times when .gclient '
1321 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001322 'if the src@ part is skipped. Note that specifying '
1323 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001324 parser.add_option('-t', '--transitive', action='store_true',
1325 help='When a revision is specified (in the DEPS file or '
1326 'with the command-line flag), transitively update '
1327 'the dependencies to the date of the given revision. '
1328 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001329 parser.add_option('-H', '--head', action='store_true',
1330 help='skips any safesync_urls specified in '
1331 'configured solutions and sync to head instead')
1332 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001333 help='Deletes from the working copy any dependencies that '
1334 'have been removed since the last sync, as long as '
1335 'there are no local modifications. When used with '
1336 '--force, such dependencies are removed even if they '
1337 'have local modifications. When used with --reset, '
1338 'all untracked directories are removed from the '
1339 'working copy, exclusing those which are explicitly '
1340 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001341 parser.add_option('-R', '--reset', action='store_true',
1342 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001343 parser.add_option('-M', '--merge', action='store_true',
1344 help='merge upstream changes instead of trying to '
1345 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001346 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1347 help='override deps for the specified (comma-separated) '
1348 'platform(s); \'all\' will process all deps_os '
1349 'references')
1350 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1351 help='Skip svn up whenever possible by requesting '
1352 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001353 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001354 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001355
1356 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001357 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001358
maruel@chromium.org307d1792010-05-31 20:03:13 +00001359 if options.revisions and options.head:
1360 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001361 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001362
1363 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('update', args)
1368
1369
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001370def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001371 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001372 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001373
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001374def CMDdiff(parser, args):
1375 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001376 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1377 help='override deps for the specified (comma-separated) '
1378 'platform(s); \'all\' will process all deps_os '
1379 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001380 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001381 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001382 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001383 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001384 if options.verbose:
1385 # Print out the .gclient file. This is longer than if we just printed the
1386 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001387 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001388 return client.RunOnDeps('diff', args)
1389
1390
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001391def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001392 """Revert all modifications in every dependencies.
1393
1394 That's the nuclear option to get back to a 'clean' state. It removes anything
1395 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001396 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1397 help='override deps for the specified (comma-separated) '
1398 'platform(s); \'all\' will process all deps_os '
1399 'references')
1400 parser.add_option('-n', '--nohooks', action='store_true',
1401 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001402 (options, args) = parser.parse_args(args)
1403 # --force is implied.
1404 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001405 options.reset = False
1406 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001407 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001408 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001409 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001410 return client.RunOnDeps('revert', args)
1411
1412
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001413def CMDrunhooks(parser, args):
1414 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001415 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1416 help='override deps for the specified (comma-separated) '
1417 'platform(s); \'all\' will process all deps_os '
1418 'references')
1419 parser.add_option('-f', '--force', action='store_true', default=True,
1420 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001421 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001422 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001423 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001424 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001425 if options.verbose:
1426 # Print out the .gclient file. This is longer than if we just printed the
1427 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001428 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001429 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001430 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001431 return client.RunOnDeps('runhooks', args)
1432
1433
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001434def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001435 """Output revision info mapping for the client and its dependencies.
1436
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001437 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001438 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001439 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1440 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001441 commit can change.
1442 """
1443 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1444 help='override deps for the specified (comma-separated) '
1445 'platform(s); \'all\' will process all deps_os '
1446 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001447 parser.add_option('-a', '--actual', action='store_true',
1448 help='gets the actual checked out revisions instead of the '
1449 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001450 parser.add_option('-s', '--snapshot', action='store_true',
1451 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001452 'version of all repositories to reproduce the tree, '
1453 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001454 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001455 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001457 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001458 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001459 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001460
1461
szager@google.comb9a78d32012-03-13 18:46:21 +00001462def CMDhookinfo(parser, args):
1463 """Output the hooks that would be run by `gclient runhooks`"""
1464 (options, args) = parser.parse_args(args)
1465 options.force = True
1466 client = GClient.LoadCurrentConfig(options)
1467 if not client:
1468 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1469 client.RunOnDeps(None, [])
1470 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1471 return 0
1472
1473
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001474def Command(name):
1475 return getattr(sys.modules[__name__], 'CMD' + name, None)
1476
1477
1478def CMDhelp(parser, args):
1479 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001480 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001481 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001482 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001483 parser.print_help()
1484 return 0
1485
1486
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001487def GenUsage(parser, command):
1488 """Modify an OptParse object with the function's documentation."""
1489 obj = Command(command)
1490 if command == 'help':
1491 command = '<command>'
1492 # OptParser.description prefer nicely non-formatted strings.
1493 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1494 usage = getattr(obj, 'usage', '')
1495 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1496 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001497
1498
maruel@chromium.org0895b752011-08-26 20:40:33 +00001499def Parser():
1500 """Returns the default parser."""
1501 parser = optparse.OptionParser(version='%prog ' + __version__)
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001502 # cygwin and some arm boards have issues with parallel sync.
1503 if sys.platform == 'cygwin' or platform.machine().startswith('arm'):
1504 jobs = 1
1505 else:
1506 jobs = 8
maruel@chromium.org41071612011-10-19 19:58:08 +00001507 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001508 help='Specify how many SCM commands can run in parallel; '
1509 'default=%default')
1510 parser.add_option('-v', '--verbose', action='count', default=0,
1511 help='Produces additional output for diagnostics. Can be '
1512 'used up to three times for more logging info.')
1513 parser.add_option('--gclientfile', dest='config_filename',
1514 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1515 help='Specify an alternate %default file')
1516 # Integrate standard options processing.
1517 old_parser = parser.parse_args
1518 def Parse(args):
1519 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001520 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1521 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001522 logging.basicConfig(level=level,
1523 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1524 options.entries_filename = options.config_filename + '_entries'
1525 if options.jobs < 1:
1526 parser.error('--jobs must be 1 or higher')
1527
1528 # These hacks need to die.
1529 if not hasattr(options, 'revisions'):
1530 # GClient.RunOnDeps expects it even if not applicable.
1531 options.revisions = []
1532 if not hasattr(options, 'head'):
1533 options.head = None
1534 if not hasattr(options, 'nohooks'):
1535 options.nohooks = True
1536 if not hasattr(options, 'deps_os'):
1537 options.deps_os = None
1538 if not hasattr(options, 'manually_grab_svn_rev'):
1539 options.manually_grab_svn_rev = None
1540 if not hasattr(options, 'force'):
1541 options.force = None
1542 return (options, args)
1543 parser.parse_args = Parse
1544 # We don't want wordwrapping in epilog (usually examples)
1545 parser.format_epilog = lambda _: parser.epilog or ''
1546 return parser
1547
1548
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001549def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001550 """Doesn't parse the arguments here, just find the right subcommand to
1551 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001552 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001553 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001554 '\nYour python version %s is unsupported, please upgrade.\n' %
1555 sys.version.split(' ', 1)[0])
1556 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001557 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001558 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001559 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1560 # operations. Python as a strong tendency to buffer sys.stdout.
1561 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001562 # Make stdout annotated with the thread ids.
1563 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001564 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001565 # Unused variable 'usage'
1566 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001567 def to_str(fn):
1568 return (
1569 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1570 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1571 cmds = (
1572 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1573 )
1574 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001575 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001576 if argv:
1577 command = Command(argv[0])
1578 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001579 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001580 GenUsage(parser, argv[0])
1581 return command(parser, argv[1:])
1582 # Not a known command. Default to help.
1583 GenUsage(parser, 'help')
1584 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001585 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001586 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001587 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001588
1589
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001590if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001591 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001592 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001593
1594# vim: ts=2:sw=2:tw=80:et: