blob: 3a4622ef6add43b60d7ef754ef522c637927ebaa [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.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000528 run_scm = command not in ('runhooks', 'recurse', 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:]
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000568 elif command is 'recurse':
569 if not isinstance(parsed_url, self.FileImpl):
570 # Skip file only checkout.
571 scm = gclient_scm.GetScmName(parsed_url)
572 if not options.scm or scm in options.scm:
573 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
574 # Pass in the SCM type as an env variable
575 env = os.environ.copy()
576 if scm:
577 env['GCLIENT_SCM'] = scm
578 if parsed_url:
579 env['GCLIENT_URL'] = parsed_url
580 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000581 try:
582 gclient_utils.CheckCallAndFilter(
583 args, cwd=cwd, env=env, print_stdout=True)
584 except subprocess2.CalledProcessError:
585 if not options.ignore:
586 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000587 else:
588 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000589
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000590 # Always parse the DEPS file.
591 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000592
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000593 self._run_is_done(file_list, parsed_url)
594
595 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000596 # Parse the dependencies of this dependency.
597 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000598 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000599
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000600 @gclient_utils.lockedmethod
601 def _run_is_done(self, file_list, parsed_url):
602 # Both these are kept for hooks that are run as a separate tree traversal.
603 self._file_list = file_list
604 self._parsed_url = parsed_url
605 self._processed = True
606
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000607 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000608 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000609 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000610 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000611 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000612 # Don't run the hook when it is above recursion_limit.
613 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000614 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000615 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000616 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000617 # TODO(maruel): If the user is using git or git-svn, then we don't know
618 # what files have changed so we always run all hooks. It'd be nice to fix
619 # that.
620 if (options.force or
621 isinstance(self.parsed_url, self.FileImpl) or
622 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000623 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000624 for hook_dict in self.deps_hooks:
625 self._RunHookAction(hook_dict, [])
626 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000627 # Run hooks on the basis of whether the files from the gclient operation
628 # match each hook's pattern.
629 for hook_dict in self.deps_hooks:
630 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000631 matching_file_list = [
632 f for f in self.file_list_and_children if pattern.search(f)
633 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000634 if matching_file_list:
635 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000636 for s in self.dependencies:
637 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000638
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000639 def _RunHookAction(self, hook_dict, matching_file_list):
640 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000641 # A single DEPS file can specify multiple hooks so this function can be
642 # called multiple times on a single Dependency.
643 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000644 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000645 logging.debug(hook_dict)
646 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000647 command = hook_dict['action'][:]
648 if command[0] == 'python':
649 # If the hook specified "python" as the first item, the action is a
650 # Python script. Run it by starting a new copy of the same
651 # interpreter.
652 command[0] = sys.executable
653
654 if '$matching_files' in command:
655 splice_index = command.index('$matching_files')
656 command[splice_index:splice_index + 1] = matching_file_list
657
maruel@chromium.org17d01792010-09-01 18:07:10 +0000658 try:
659 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000660 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000661 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000662 # Use a discrete exit status code of 2 to indicate that a hook action
663 # failed. Users of this script may wish to treat hook action failures
664 # differently from VC failures.
665 print >> sys.stderr, 'Error: %s' % str(e)
666 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000667
maruel@chromium.org0d812442010-08-10 12:41:08 +0000668 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000669 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000670 dependencies = self.dependencies
671 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000672 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000673 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000674 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000675 for i in d.subtree(include_all):
676 yield i
677
678 def depth_first_tree(self):
679 """Depth-first recursion including the root node."""
680 yield self
681 for i in self.dependencies:
682 for j in i.depth_first_tree():
683 if j.should_process:
684 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000685
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000686 @gclient_utils.lockedmethod
687 def add_dependency(self, new_dep):
688 self._dependencies.append(new_dep)
689
690 @gclient_utils.lockedmethod
691 def _mark_as_parsed(self, new_hooks):
692 self._deps_hooks.extend(new_hooks)
693 self._deps_parsed = True
694
maruel@chromium.org68988972011-09-20 14:11:42 +0000695 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000696 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000697 def dependencies(self):
698 return tuple(self._dependencies)
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 deps_hooks(self):
703 return tuple(self._deps_hooks)
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 parsed_url(self):
708 return self._parsed_url
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 deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000713 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000714 return self._deps_parsed
715
716 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000717 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000718 def processed(self):
719 return self._processed
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 hooks_ran(self):
724 return self._hooks_ran
725
726 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000727 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000728 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000729 return tuple(self._file_list)
730
731 @property
732 def file_list_and_children(self):
733 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000734 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000735 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000736 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000737
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000738 def __str__(self):
739 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000740 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000741 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000742 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000743 # First try the native property if it exists.
744 if hasattr(self, '_' + i):
745 value = getattr(self, '_' + i, False)
746 else:
747 value = getattr(self, i, False)
748 if value:
749 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000750
751 for d in self.dependencies:
752 out.extend([' ' + x for x in str(d).splitlines()])
753 out.append('')
754 return '\n'.join(out)
755
756 def __repr__(self):
757 return '%s: %s' % (self.name, self.url)
758
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000759 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000760 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000761 out = '%s(%s)' % (self.name, self.url)
762 i = self.parent
763 while i and i.name:
764 out = '%s(%s) -> %s' % (i.name, i.url, out)
765 i = i.parent
766 return out
767
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000768
769class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000770 """Object that represent a gclient checkout. A tree of Dependency(), one per
771 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000772
773 DEPS_OS_CHOICES = {
774 "win32": "win",
775 "win": "win",
776 "cygwin": "win",
777 "darwin": "mac",
778 "mac": "mac",
779 "unix": "unix",
780 "linux": "unix",
781 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000782 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000783 }
784
785 DEFAULT_CLIENT_FILE_TEXT = ("""\
786solutions = [
787 { "name" : "%(solution_name)s",
788 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000789 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000790 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000791 "custom_deps" : {
792 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000793 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000794 },
795]
796""")
797
798 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
799 { "name" : "%(solution_name)s",
800 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000801 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000802 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000803 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000804%(solution_deps)s },
805 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000806 },
807""")
808
809 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
810# Snapshot generated with gclient revinfo --snapshot
811solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000812%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000813""")
814
815 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000816 # Do not change previous behavior. Only solution level and immediate DEPS
817 # are processed.
818 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000819 Dependency.__init__(self, None, None, None, None, True, None, None,
820 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000821 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000822 if options.deps_os:
823 enforced_os = options.deps_os.split(',')
824 else:
825 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
826 if 'all' in enforced_os:
827 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000828 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000829 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000830 self.config_content = None
831
832 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000833 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000834 config_dict = {}
835 self.config_content = content
836 try:
837 exec(content, config_dict)
838 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000839 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000840
841 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000842 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000843 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000844 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000845 self, s['name'], s['url'],
846 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000847 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000848 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000849 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000850 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000851 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000852 except KeyError:
853 raise gclient_utils.Error('Invalid .gclient file. Solution is '
854 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000855 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
856 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000857
858 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000859 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000860 self._options.config_filename),
861 self.config_content)
862
863 @staticmethod
864 def LoadCurrentConfig(options):
865 """Searches for and loads a .gclient file relative to the current working
866 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000867 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000868 if not path:
869 return None
870 client = GClient(path, options)
871 client.SetConfig(gclient_utils.FileRead(
872 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000873
874 if (options.revisions and
875 len(client.dependencies) > 1 and
876 any('@' not in r for r in options.revisions)):
877 print >> sys.stderr, (
878 'You must specify the full solution name like --revision %s@%s\n'
879 'when you have multiple solutions setup in your .gclient file.\n'
880 'Other solutions present are: %s.') % (
881 client.dependencies[0].name,
882 options.revisions[0],
883 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000884 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000885
nsylvain@google.comefc80932011-05-31 21:27:56 +0000886 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000887 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000888 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
889 'solution_name': solution_name,
890 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000891 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000892 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000893 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000894 })
895
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000896 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000897 """Creates a .gclient_entries file to record the list of unique checkouts.
898
899 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000900 """
901 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
902 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000903 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000904 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000905 # Skip over File() dependencies as we can't version them.
906 if not isinstance(entry.parsed_url, self.FileImpl):
907 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
908 pprint.pformat(entry.parsed_url))
909 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000910 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000911 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000912 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000913
914 def _ReadEntries(self):
915 """Read the .gclient_entries file for the given client.
916
917 Returns:
918 A sequence of solution names, which will be empty if there is the
919 entries file hasn't been created yet.
920 """
921 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000922 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000923 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000924 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000925 try:
926 exec(gclient_utils.FileRead(filename), scope)
927 except SyntaxError, e:
928 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000929 return scope['entries']
930
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000931 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000932 """Checks for revision overrides."""
933 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000934 if self._options.head:
935 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000936 # Do not check safesync_url if one or more --revision flag is specified.
937 if not self._options.revisions:
938 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000939 if not s.managed:
940 self._options.revisions.append('%s@unmanaged' % s.name)
941 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000942 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000943 if not self._options.revisions:
944 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000945 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000946 index = 0
947 for revision in self._options.revisions:
948 if not '@' in revision:
949 # Support for --revision 123
950 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000951 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000952 if not sol in solutions_names:
953 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
954 print >> sys.stderr, ('Please fix your script, having invalid '
955 '--revision flags will soon considered an error.')
956 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000957 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000958 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000959 return revision_overrides
960
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000961 def _ApplySafeSyncRev(self, dep):
962 """Finds a valid revision from the content of the safesync_url and apply it
963 by appending revisions to the revision list. Throws if revision appears to
964 be invalid for the given |dep|."""
965 assert len(dep.safesync_url) > 0
966 handle = urllib.urlopen(dep.safesync_url)
967 rev = handle.read().strip()
968 handle.close()
969 if not rev:
970 raise gclient_utils.Error(
971 'It appears your safesync_url (%s) is not working properly\n'
972 '(as it returned an empty response). Check your config.' %
973 dep.safesync_url)
974 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
975 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
976 if self._options.verbose:
977 print('Using safesync_url revision: %s.\n' % safe_rev)
978 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
979
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000980 def RunOnDeps(self, command, args):
981 """Runs a command on each dependency in a client and its dependencies.
982
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000983 Args:
984 command: The command to use (e.g., 'status' or 'diff')
985 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000986 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000987 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000988 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000989 revision_overrides = {}
990 # It's unnecessary to check for revision overrides for 'recurse'.
991 # Save a few seconds by not calling _EnforceRevisions() in that case.
992 if command is not 'recurse':
993 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000994 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000995 # Disable progress for non-tty stdout.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000996 if (sys.stdout.isatty() and not self._options.verbose):
997 if command in ('update', 'revert'):
998 pm = Progress('Syncing projects', 1)
999 elif command is 'recurse':
1000 pm = Progress(' '.join(args), 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001001 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001002 for s in self.dependencies:
1003 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001004 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001005
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001006 # Once all the dependencies have been processed, it's now safe to run the
1007 # hooks.
1008 if not self._options.nohooks:
1009 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001010
1011 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001012 # Notify the user if there is an orphaned entry in their working copy.
1013 # Only delete the directory if there are no changes in it, and
1014 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001015 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001016 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001017 if not prev_url:
1018 # entry must have been overridden via .gclient custom_deps
1019 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001020 # Fix path separator on Windows.
1021 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001022 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001023 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001024 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001025 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001026 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001027 scm.status(self._options, [], file_list)
1028 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001029 if (not self._options.delete_unversioned_trees or
1030 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001031 # There are modified files in this entry. Keep warning until
1032 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001033 print(('\nWARNING: \'%s\' is no longer part of this client. '
1034 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001035 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001036 else:
1037 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001038 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001039 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001040 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001041 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001042 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001043 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044
1045 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001046 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001047 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001048 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001049 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001050 for s in self.dependencies:
1051 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001052 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001054 def GetURLAndRev(dep):
1055 """Returns the revision-qualified SCM url for a Dependency."""
1056 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001057 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001058 if isinstance(dep.parsed_url, self.FileImpl):
1059 original_url = dep.parsed_url.file_location
1060 else:
1061 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001062 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001063 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001064 if not os.path.isdir(scm.checkout_path):
1065 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001066 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001067
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001068 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001069 new_gclient = ''
1070 # First level at .gclient
1071 for d in self.dependencies:
1072 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001073 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001074 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001075 for d in dep.dependencies:
1076 entries[d.name] = GetURLAndRev(d)
1077 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001078 GrabDeps(d)
1079 custom_deps = []
1080 for k in sorted(entries.keys()):
1081 if entries[k]:
1082 # Quotes aren't escaped...
1083 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1084 else:
1085 custom_deps.append(' \"%s\": None,\n' % k)
1086 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1087 'solution_name': d.name,
1088 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001089 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001090 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001091 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001092 'solution_deps': ''.join(custom_deps),
1093 }
1094 # Print the snapshot configuration file
1095 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001096 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001097 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001098 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001099 if self._options.actual:
1100 entries[d.name] = GetURLAndRev(d)
1101 else:
1102 entries[d.name] = d.parsed_url
1103 keys = sorted(entries.keys())
1104 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001105 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001106 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001107
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001108 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001109 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001110 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001111
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001112 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001113 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001114 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001115 return self._root_dir
1116
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001117 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001118 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001119 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001120 return self._enforced_os
1121
maruel@chromium.org68988972011-09-20 14:11:42 +00001122 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001123 def recursion_limit(self):
1124 """How recursive can each dependencies in DEPS file can load DEPS file."""
1125 return self._recursion_limit
1126
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001127
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001128#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129
1130
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001131def CMDcleanup(parser, args):
1132 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001133
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001134Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001135"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001136 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1137 help='override deps for the specified (comma-separated) '
1138 'platform(s); \'all\' will process all deps_os '
1139 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001140 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001141 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001142 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001143 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001144 if options.verbose:
1145 # Print out the .gclient file. This is longer than if we just printed the
1146 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001147 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001148 return client.RunOnDeps('cleanup', args)
1149
1150
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001151@attr('usage', '[command] [args ...]')
1152def CMDrecurse(parser, args):
1153 """Operates on all the entries.
1154
1155 Runs a shell command on all entries.
1156 """
1157 # Stop parsing at the first non-arg so that these go through to the command
1158 parser.disable_interspersed_args()
1159 parser.add_option('-s', '--scm', action='append', default=[],
1160 help='choose scm types to operate upon')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001161 parser.add_option('-i', '--ignore', action='store_true',
1162 help='continue processing in case of non zero return code')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001163 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001164 if not args:
1165 print >> sys.stderr, 'Need to supply a command!'
1166 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001167 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1168 if not root_and_entries:
1169 print >> sys.stderr, (
1170 'You need to run gclient sync at least once to use \'recurse\'.\n'
1171 'This is because .gclient_entries needs to exist and be up to date.')
1172 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001173
1174 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001175 scm_set = set()
1176 for scm in options.scm:
1177 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001178 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001179
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001180 options.nohooks = True
1181 client = GClient.LoadCurrentConfig(options)
1182 return client.RunOnDeps('recurse', args)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001183
1184
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001185@attr('usage', '[args ...]')
1186def CMDfetch(parser, args):
1187 """Fetches upstream commits for all modules.
1188
1189Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1190"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001191 (options, args) = parser.parse_args(args)
1192 args = ['-j%d' % options.jobs, '-s', 'git', 'git', 'fetch'] + args
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001193 return CMDrecurse(parser, args)
1194
1195
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001196@attr('usage', '[url] [safesync url]')
1197def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001198 """Create a .gclient file in the current directory.
1199
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001200This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001201top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001202modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001203provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001204URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001205"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001206 parser.add_option('--spec',
1207 help='create a gclient file containing the provided '
1208 'string. Due to Cygwin/Python brokenness, it '
1209 'probably can\'t contain any newlines.')
1210 parser.add_option('--name',
1211 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001212 parser.add_option('--deps-file', default='DEPS',
1213 help='overrides the default name for the DEPS file for the'
1214 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001215 parser.add_option('--unmanaged', action='store_true', default=False,
1216 help='overrides the default behavior to make it possible '
1217 'to have the main solution untouched by gclient '
1218 '(gclient will check out unmanaged dependencies but '
1219 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001220 parser.add_option('--git-deps', action='store_true',
1221 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001222 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001223 if ((options.spec and args) or len(args) > 2 or
1224 (not options.spec and not args)):
1225 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1226
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001227 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001228 if options.spec:
1229 client.SetConfig(options.spec)
1230 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001231 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001232 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001233 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001234 if name.endswith('.git'):
1235 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001236 else:
1237 # specify an alternate relpath for the given URL.
1238 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001239 deps_file = options.deps_file
1240 if options.git_deps:
1241 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001242 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001243 if len(args) > 1:
1244 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001245 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1246 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001247 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001248 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001249
1250
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001251@attr('epilog', """Example:
1252 gclient pack > patch.txt
1253 generate simple patch for configured client and dependences
1254""")
1255def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001256 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001257
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001258Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001259dependencies, and performs minimal postprocessing of the output. The
1260resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001261checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001262"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001263 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1264 help='override deps for the specified (comma-separated) '
1265 'platform(s); \'all\' will process all deps_os '
1266 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001267 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001268 client = GClient.LoadCurrentConfig(options)
1269 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001270 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001271 if options.verbose:
1272 # Print out the .gclient file. This is longer than if we just printed the
1273 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001274 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001275 return client.RunOnDeps('pack', args)
1276
1277
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001278def CMDstatus(parser, args):
1279 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001280 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1281 help='override deps for the specified (comma-separated) '
1282 'platform(s); \'all\' will process all deps_os '
1283 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001284 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001285 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001286 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001287 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001288 if options.verbose:
1289 # Print out the .gclient file. This is longer than if we just printed the
1290 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001291 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001292 return client.RunOnDeps('status', args)
1293
1294
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001295@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001296 gclient sync
1297 update files from SCM according to current configuration,
1298 *for modules which have changed since last update or sync*
1299 gclient sync --force
1300 update files from SCM according to current configuration, for
1301 all modules (useful for recovering files deleted from local copy)
1302 gclient sync --revision src@31000
1303 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001304""")
1305def CMDsync(parser, args):
1306 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001307 parser.add_option('-f', '--force', action='store_true',
1308 help='force update even for unchanged modules')
1309 parser.add_option('-n', '--nohooks', action='store_true',
1310 help='don\'t run hooks after the update is complete')
1311 parser.add_option('-r', '--revision', action='append',
1312 dest='revisions', metavar='REV', default=[],
1313 help='Enforces revision/hash for the solutions with the '
1314 'format src@rev. The src@ part is optional and can be '
1315 'skipped. -r can be used multiple times when .gclient '
1316 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001317 'if the src@ part is skipped. Note that specifying '
1318 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001319 parser.add_option('-t', '--transitive', action='store_true',
1320 help='When a revision is specified (in the DEPS file or '
1321 'with the command-line flag), transitively update '
1322 'the dependencies to the date of the given revision. '
1323 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001324 parser.add_option('-H', '--head', action='store_true',
1325 help='skips any safesync_urls specified in '
1326 'configured solutions and sync to head instead')
1327 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001328 help='Deletes from the working copy any dependencies that '
1329 'have been removed since the last sync, as long as '
1330 'there are no local modifications. When used with '
1331 '--force, such dependencies are removed even if they '
1332 'have local modifications. When used with --reset, '
1333 'all untracked directories are removed from the '
1334 'working copy, exclusing those which are explicitly '
1335 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001336 parser.add_option('-R', '--reset', action='store_true',
1337 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001338 parser.add_option('-M', '--merge', action='store_true',
1339 help='merge upstream changes instead of trying to '
1340 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001341 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1342 help='override deps for the specified (comma-separated) '
1343 'platform(s); \'all\' will process all deps_os '
1344 'references')
1345 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1346 help='Skip svn up whenever possible by requesting '
1347 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001348 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001349 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001350
1351 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001352 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001353
maruel@chromium.org307d1792010-05-31 20:03:13 +00001354 if options.revisions and options.head:
1355 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001356 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001357
1358 if options.verbose:
1359 # Print out the .gclient file. This is longer than if we just printed the
1360 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001361 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001362 return client.RunOnDeps('update', args)
1363
1364
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001365def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001366 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001367 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001368
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001369def CMDdiff(parser, args):
1370 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001371 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1372 help='override deps for the specified (comma-separated) '
1373 'platform(s); \'all\' will process all deps_os '
1374 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001375 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001376 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001377 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001378 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001379 if options.verbose:
1380 # Print out the .gclient file. This is longer than if we just printed the
1381 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001382 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001383 return client.RunOnDeps('diff', args)
1384
1385
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001386def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001387 """Revert all modifications in every dependencies.
1388
1389 That's the nuclear option to get back to a 'clean' state. It removes anything
1390 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001391 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1392 help='override deps for the specified (comma-separated) '
1393 'platform(s); \'all\' will process all deps_os '
1394 'references')
1395 parser.add_option('-n', '--nohooks', action='store_true',
1396 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001397 (options, args) = parser.parse_args(args)
1398 # --force is implied.
1399 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001400 options.reset = False
1401 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001402 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001403 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001404 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001405 return client.RunOnDeps('revert', args)
1406
1407
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001408def CMDrunhooks(parser, args):
1409 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001410 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1411 help='override deps for the specified (comma-separated) '
1412 'platform(s); \'all\' will process all deps_os '
1413 'references')
1414 parser.add_option('-f', '--force', action='store_true', default=True,
1415 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001416 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001417 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001418 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001419 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001420 if options.verbose:
1421 # Print out the .gclient file. This is longer than if we just printed the
1422 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001423 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001424 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001425 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001426 return client.RunOnDeps('runhooks', args)
1427
1428
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001429def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001430 """Output revision info mapping for the client and its dependencies.
1431
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001432 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001433 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001434 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1435 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001436 commit can change.
1437 """
1438 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1439 help='override deps for the specified (comma-separated) '
1440 'platform(s); \'all\' will process all deps_os '
1441 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001442 parser.add_option('-a', '--actual', action='store_true',
1443 help='gets the actual checked out revisions instead of the '
1444 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001445 parser.add_option('-s', '--snapshot', action='store_true',
1446 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001447 'version of all repositories to reproduce the tree, '
1448 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001449 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001450 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001451 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001452 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001453 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001454 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001455
1456
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001457def Command(name):
1458 return getattr(sys.modules[__name__], 'CMD' + name, None)
1459
1460
1461def CMDhelp(parser, args):
1462 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001463 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001464 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001465 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001466 parser.print_help()
1467 return 0
1468
1469
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001470def GenUsage(parser, command):
1471 """Modify an OptParse object with the function's documentation."""
1472 obj = Command(command)
1473 if command == 'help':
1474 command = '<command>'
1475 # OptParser.description prefer nicely non-formatted strings.
1476 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1477 usage = getattr(obj, 'usage', '')
1478 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1479 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001480
1481
maruel@chromium.org0895b752011-08-26 20:40:33 +00001482def Parser():
1483 """Returns the default parser."""
1484 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org41071612011-10-19 19:58:08 +00001485 # cygwin has issues with parallel sync
1486 jobs = 1 if sys.platform == 'cygwin' else 8
1487 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001488 help='Specify how many SCM commands can run in parallel; '
1489 'default=%default')
1490 parser.add_option('-v', '--verbose', action='count', default=0,
1491 help='Produces additional output for diagnostics. Can be '
1492 'used up to three times for more logging info.')
1493 parser.add_option('--gclientfile', dest='config_filename',
1494 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1495 help='Specify an alternate %default file')
1496 # Integrate standard options processing.
1497 old_parser = parser.parse_args
1498 def Parse(args):
1499 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001500 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1501 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001502 logging.basicConfig(level=level,
1503 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1504 options.entries_filename = options.config_filename + '_entries'
1505 if options.jobs < 1:
1506 parser.error('--jobs must be 1 or higher')
1507
1508 # These hacks need to die.
1509 if not hasattr(options, 'revisions'):
1510 # GClient.RunOnDeps expects it even if not applicable.
1511 options.revisions = []
1512 if not hasattr(options, 'head'):
1513 options.head = None
1514 if not hasattr(options, 'nohooks'):
1515 options.nohooks = True
1516 if not hasattr(options, 'deps_os'):
1517 options.deps_os = None
1518 if not hasattr(options, 'manually_grab_svn_rev'):
1519 options.manually_grab_svn_rev = None
1520 if not hasattr(options, 'force'):
1521 options.force = None
1522 return (options, args)
1523 parser.parse_args = Parse
1524 # We don't want wordwrapping in epilog (usually examples)
1525 parser.format_epilog = lambda _: parser.epilog or ''
1526 return parser
1527
1528
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001529def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001530 """Doesn't parse the arguments here, just find the right subcommand to
1531 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001532 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001533 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001534 '\nYour python version %s is unsupported, please upgrade.\n' %
1535 sys.version.split(' ', 1)[0])
1536 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001537 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001538 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001539 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1540 # operations. Python as a strong tendency to buffer sys.stdout.
1541 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001542 # Make stdout annotated with the thread ids.
1543 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001544 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001545 # Unused variable 'usage'
1546 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001547 def to_str(fn):
1548 return (
1549 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1550 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1551 cmds = (
1552 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1553 )
1554 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001555 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001556 if argv:
1557 command = Command(argv[0])
1558 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001559 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001560 GenUsage(parser, argv[0])
1561 return command(parser, argv[1:])
1562 # Not a known command. Default to help.
1563 GenUsage(parser, 'help')
1564 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001565 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001566 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001567 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001568
1569
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001570if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001571 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001572 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001573
1574# vim: ts=2:sw=2:tw=80:et: