blob: 0cfb26dd7d8a13d881157a833ef4da55f0a7bb2a [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):
581 gclient_utils.CheckCallAndFilter(
582 args, cwd=cwd, env=env, print_stdout=True)
583 else:
584 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000585
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000586 # Always parse the DEPS file.
587 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000588
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000589 self._run_is_done(file_list, parsed_url)
590
591 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000592 # Parse the dependencies of this dependency.
593 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000594 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000595
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000596 @gclient_utils.lockedmethod
597 def _run_is_done(self, file_list, parsed_url):
598 # Both these are kept for hooks that are run as a separate tree traversal.
599 self._file_list = file_list
600 self._parsed_url = parsed_url
601 self._processed = True
602
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000603 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000604 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000605 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000606 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000607 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000608 # Don't run the hook when it is above recursion_limit.
609 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000610 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000611 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000612 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000613 # TODO(maruel): If the user is using git or git-svn, then we don't know
614 # what files have changed so we always run all hooks. It'd be nice to fix
615 # that.
616 if (options.force or
617 isinstance(self.parsed_url, self.FileImpl) or
618 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000619 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000620 for hook_dict in self.deps_hooks:
621 self._RunHookAction(hook_dict, [])
622 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000623 # Run hooks on the basis of whether the files from the gclient operation
624 # match each hook's pattern.
625 for hook_dict in self.deps_hooks:
626 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000627 matching_file_list = [
628 f for f in self.file_list_and_children if pattern.search(f)
629 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000630 if matching_file_list:
631 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000632 for s in self.dependencies:
633 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000634
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000635 def _RunHookAction(self, hook_dict, matching_file_list):
636 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000637 # A single DEPS file can specify multiple hooks so this function can be
638 # called multiple times on a single Dependency.
639 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000640 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000641 logging.debug(hook_dict)
642 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000643 command = hook_dict['action'][:]
644 if command[0] == 'python':
645 # If the hook specified "python" as the first item, the action is a
646 # Python script. Run it by starting a new copy of the same
647 # interpreter.
648 command[0] = sys.executable
649
650 if '$matching_files' in command:
651 splice_index = command.index('$matching_files')
652 command[splice_index:splice_index + 1] = matching_file_list
653
maruel@chromium.org17d01792010-09-01 18:07:10 +0000654 try:
655 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000656 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000657 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000658 # Use a discrete exit status code of 2 to indicate that a hook action
659 # failed. Users of this script may wish to treat hook action failures
660 # differently from VC failures.
661 print >> sys.stderr, 'Error: %s' % str(e)
662 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000663
maruel@chromium.org0d812442010-08-10 12:41:08 +0000664 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000665 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000666 dependencies = self.dependencies
667 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000668 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000669 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000670 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000671 for i in d.subtree(include_all):
672 yield i
673
674 def depth_first_tree(self):
675 """Depth-first recursion including the root node."""
676 yield self
677 for i in self.dependencies:
678 for j in i.depth_first_tree():
679 if j.should_process:
680 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000681
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000682 @gclient_utils.lockedmethod
683 def add_dependency(self, new_dep):
684 self._dependencies.append(new_dep)
685
686 @gclient_utils.lockedmethod
687 def _mark_as_parsed(self, new_hooks):
688 self._deps_hooks.extend(new_hooks)
689 self._deps_parsed = True
690
maruel@chromium.org68988972011-09-20 14:11:42 +0000691 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000692 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000693 def dependencies(self):
694 return tuple(self._dependencies)
695
696 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000697 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000698 def deps_hooks(self):
699 return tuple(self._deps_hooks)
700
701 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000702 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000703 def parsed_url(self):
704 return self._parsed_url
705
706 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000707 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000708 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000709 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000710 return self._deps_parsed
711
712 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000713 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000714 def processed(self):
715 return self._processed
716
717 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000718 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000719 def hooks_ran(self):
720 return self._hooks_ran
721
722 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000723 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000724 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000725 return tuple(self._file_list)
726
727 @property
728 def file_list_and_children(self):
729 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000730 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000731 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000732 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000733
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000734 def __str__(self):
735 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000736 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000737 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000738 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000739 # First try the native property if it exists.
740 if hasattr(self, '_' + i):
741 value = getattr(self, '_' + i, False)
742 else:
743 value = getattr(self, i, False)
744 if value:
745 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000746
747 for d in self.dependencies:
748 out.extend([' ' + x for x in str(d).splitlines()])
749 out.append('')
750 return '\n'.join(out)
751
752 def __repr__(self):
753 return '%s: %s' % (self.name, self.url)
754
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000755 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000756 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000757 out = '%s(%s)' % (self.name, self.url)
758 i = self.parent
759 while i and i.name:
760 out = '%s(%s) -> %s' % (i.name, i.url, out)
761 i = i.parent
762 return out
763
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000764
765class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000766 """Object that represent a gclient checkout. A tree of Dependency(), one per
767 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000768
769 DEPS_OS_CHOICES = {
770 "win32": "win",
771 "win": "win",
772 "cygwin": "win",
773 "darwin": "mac",
774 "mac": "mac",
775 "unix": "unix",
776 "linux": "unix",
777 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000778 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000779 }
780
781 DEFAULT_CLIENT_FILE_TEXT = ("""\
782solutions = [
783 { "name" : "%(solution_name)s",
784 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000785 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000786 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000787 "custom_deps" : {
788 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000789 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000790 },
791]
792""")
793
794 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
795 { "name" : "%(solution_name)s",
796 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000797 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000798 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000799 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000800%(solution_deps)s },
801 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000802 },
803""")
804
805 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
806# Snapshot generated with gclient revinfo --snapshot
807solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000808%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000809""")
810
811 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000812 # Do not change previous behavior. Only solution level and immediate DEPS
813 # are processed.
814 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000815 Dependency.__init__(self, None, None, None, None, True, None, None,
816 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000817 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000818 if options.deps_os:
819 enforced_os = options.deps_os.split(',')
820 else:
821 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
822 if 'all' in enforced_os:
823 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000824 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000825 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000826 self.config_content = None
827
828 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000829 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000830 config_dict = {}
831 self.config_content = content
832 try:
833 exec(content, config_dict)
834 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000835 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000836
837 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000838 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000839 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000840 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000841 self, s['name'], s['url'],
842 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000843 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000844 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000845 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000846 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000847 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000848 except KeyError:
849 raise gclient_utils.Error('Invalid .gclient file. Solution is '
850 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000851 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
852 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000853
854 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000855 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000856 self._options.config_filename),
857 self.config_content)
858
859 @staticmethod
860 def LoadCurrentConfig(options):
861 """Searches for and loads a .gclient file relative to the current working
862 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000863 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000864 if not path:
865 return None
866 client = GClient(path, options)
867 client.SetConfig(gclient_utils.FileRead(
868 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000869
870 if (options.revisions and
871 len(client.dependencies) > 1 and
872 any('@' not in r for r in options.revisions)):
873 print >> sys.stderr, (
874 'You must specify the full solution name like --revision %s@%s\n'
875 'when you have multiple solutions setup in your .gclient file.\n'
876 'Other solutions present are: %s.') % (
877 client.dependencies[0].name,
878 options.revisions[0],
879 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000880 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000881
nsylvain@google.comefc80932011-05-31 21:27:56 +0000882 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000883 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000884 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
885 'solution_name': solution_name,
886 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000887 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000888 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000889 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000890 })
891
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000892 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000893 """Creates a .gclient_entries file to record the list of unique checkouts.
894
895 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000896 """
897 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
898 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000899 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000900 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000901 # Skip over File() dependencies as we can't version them.
902 if not isinstance(entry.parsed_url, self.FileImpl):
903 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
904 pprint.pformat(entry.parsed_url))
905 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000906 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000907 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000908 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000909
910 def _ReadEntries(self):
911 """Read the .gclient_entries file for the given client.
912
913 Returns:
914 A sequence of solution names, which will be empty if there is the
915 entries file hasn't been created yet.
916 """
917 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000918 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000919 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000920 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000921 try:
922 exec(gclient_utils.FileRead(filename), scope)
923 except SyntaxError, e:
924 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000925 return scope['entries']
926
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000927 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000928 """Checks for revision overrides."""
929 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000930 if self._options.head:
931 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000932 # Do not check safesync_url if one or more --revision flag is specified.
933 if not self._options.revisions:
934 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000935 if not s.managed:
936 self._options.revisions.append('%s@unmanaged' % s.name)
937 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000938 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000939 if not self._options.revisions:
940 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000941 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000942 index = 0
943 for revision in self._options.revisions:
944 if not '@' in revision:
945 # Support for --revision 123
946 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000947 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000948 if not sol in solutions_names:
949 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
950 print >> sys.stderr, ('Please fix your script, having invalid '
951 '--revision flags will soon considered an error.')
952 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000953 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000954 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000955 return revision_overrides
956
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000957 def _ApplySafeSyncRev(self, dep):
958 """Finds a valid revision from the content of the safesync_url and apply it
959 by appending revisions to the revision list. Throws if revision appears to
960 be invalid for the given |dep|."""
961 assert len(dep.safesync_url) > 0
962 handle = urllib.urlopen(dep.safesync_url)
963 rev = handle.read().strip()
964 handle.close()
965 if not rev:
966 raise gclient_utils.Error(
967 'It appears your safesync_url (%s) is not working properly\n'
968 '(as it returned an empty response). Check your config.' %
969 dep.safesync_url)
970 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
971 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
972 if self._options.verbose:
973 print('Using safesync_url revision: %s.\n' % safe_rev)
974 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
975
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000976 def RunOnDeps(self, command, args):
977 """Runs a command on each dependency in a client and its dependencies.
978
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000979 Args:
980 command: The command to use (e.g., 'status' or 'diff')
981 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000983 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000984 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000985 revision_overrides = {}
986 # It's unnecessary to check for revision overrides for 'recurse'.
987 # Save a few seconds by not calling _EnforceRevisions() in that case.
988 if command is not 'recurse':
989 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000990 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000991 # Disable progress for non-tty stdout.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000992 if (sys.stdout.isatty() and not self._options.verbose):
993 if command in ('update', 'revert'):
994 pm = Progress('Syncing projects', 1)
995 elif command is 'recurse':
996 pm = Progress(' '.join(args), 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000997 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000998 for s in self.dependencies:
999 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001000 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001001
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001002 # Once all the dependencies have been processed, it's now safe to run the
1003 # hooks.
1004 if not self._options.nohooks:
1005 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001006
1007 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001008 # Notify the user if there is an orphaned entry in their working copy.
1009 # Only delete the directory if there are no changes in it, and
1010 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001011 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001012 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001013 if not prev_url:
1014 # entry must have been overridden via .gclient custom_deps
1015 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001016 # Fix path separator on Windows.
1017 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001018 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001019 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001020 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001021 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001022 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001023 scm.status(self._options, [], file_list)
1024 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001025 if (not self._options.delete_unversioned_trees or
1026 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001027 # There are modified files in this entry. Keep warning until
1028 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001029 print(('\nWARNING: \'%s\' is no longer part of this client. '
1030 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001031 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001032 else:
1033 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001034 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001035 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001036 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001037 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001038 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001039 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040
1041 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001042 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001043 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001044 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001045 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001046 for s in self.dependencies:
1047 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001048 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001050 def GetURLAndRev(dep):
1051 """Returns the revision-qualified SCM url for a Dependency."""
1052 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001053 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001054 if isinstance(dep.parsed_url, self.FileImpl):
1055 original_url = dep.parsed_url.file_location
1056 else:
1057 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001058 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001059 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001060 if not os.path.isdir(scm.checkout_path):
1061 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001062 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001063
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001064 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001065 new_gclient = ''
1066 # First level at .gclient
1067 for d in self.dependencies:
1068 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001069 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001070 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001071 for d in dep.dependencies:
1072 entries[d.name] = GetURLAndRev(d)
1073 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001074 GrabDeps(d)
1075 custom_deps = []
1076 for k in sorted(entries.keys()):
1077 if entries[k]:
1078 # Quotes aren't escaped...
1079 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1080 else:
1081 custom_deps.append(' \"%s\": None,\n' % k)
1082 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1083 'solution_name': d.name,
1084 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001085 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001086 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001087 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001088 'solution_deps': ''.join(custom_deps),
1089 }
1090 # Print the snapshot configuration file
1091 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001092 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001093 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001094 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001095 if self._options.actual:
1096 entries[d.name] = GetURLAndRev(d)
1097 else:
1098 entries[d.name] = d.parsed_url
1099 keys = sorted(entries.keys())
1100 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001101 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001102 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001103
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001104 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001105 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001106 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001107
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001108 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001109 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001110 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001111 return self._root_dir
1112
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001113 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001114 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001115 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001116 return self._enforced_os
1117
maruel@chromium.org68988972011-09-20 14:11:42 +00001118 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001119 def recursion_limit(self):
1120 """How recursive can each dependencies in DEPS file can load DEPS file."""
1121 return self._recursion_limit
1122
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001123
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001124#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001125
1126
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001127def CMDcleanup(parser, args):
1128 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001129
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001130Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001131"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001132 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1133 help='override deps for the specified (comma-separated) '
1134 'platform(s); \'all\' will process all deps_os '
1135 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001136 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001137 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001138 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001139 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001140 if options.verbose:
1141 # Print out the .gclient file. This is longer than if we just printed the
1142 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001143 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001144 return client.RunOnDeps('cleanup', args)
1145
1146
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001147@attr('usage', '[command] [args ...]')
1148def CMDrecurse(parser, args):
1149 """Operates on all the entries.
1150
1151 Runs a shell command on all entries.
1152 """
1153 # Stop parsing at the first non-arg so that these go through to the command
1154 parser.disable_interspersed_args()
1155 parser.add_option('-s', '--scm', action='append', default=[],
1156 help='choose scm types to operate upon')
1157 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001158 if not args:
1159 print >> sys.stderr, 'Need to supply a command!'
1160 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001161 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1162 if not root_and_entries:
1163 print >> sys.stderr, (
1164 'You need to run gclient sync at least once to use \'recurse\'.\n'
1165 'This is because .gclient_entries needs to exist and be up to date.')
1166 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001167
1168 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001169 scm_set = set()
1170 for scm in options.scm:
1171 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001172 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001173
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001174 options.nohooks = True
1175 client = GClient.LoadCurrentConfig(options)
1176 return client.RunOnDeps('recurse', args)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001177
1178
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001179@attr('usage', '[args ...]')
1180def CMDfetch(parser, args):
1181 """Fetches upstream commits for all modules.
1182
1183Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1184"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001185 (options, args) = parser.parse_args(args)
1186 args = ['-j%d' % options.jobs, '-s', 'git', 'git', 'fetch'] + args
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001187 return CMDrecurse(parser, args)
1188
1189
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001190@attr('usage', '[url] [safesync url]')
1191def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001192 """Create a .gclient file in the current directory.
1193
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001194This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001195top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001196modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001197provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001198URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001199"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001200 parser.add_option('--spec',
1201 help='create a gclient file containing the provided '
1202 'string. Due to Cygwin/Python brokenness, it '
1203 'probably can\'t contain any newlines.')
1204 parser.add_option('--name',
1205 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001206 parser.add_option('--deps-file', default='DEPS',
1207 help='overrides the default name for the DEPS file for the'
1208 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001209 parser.add_option('--unmanaged', action='store_true', default=False,
1210 help='overrides the default behavior to make it possible '
1211 'to have the main solution untouched by gclient '
1212 '(gclient will check out unmanaged dependencies but '
1213 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001214 parser.add_option('--git-deps', action='store_true',
1215 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001216 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001217 if ((options.spec and args) or len(args) > 2 or
1218 (not options.spec and not args)):
1219 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1220
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001221 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001222 if options.spec:
1223 client.SetConfig(options.spec)
1224 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001225 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001226 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001227 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001228 if name.endswith('.git'):
1229 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001230 else:
1231 # specify an alternate relpath for the given URL.
1232 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001233 deps_file = options.deps_file
1234 if options.git_deps:
1235 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001236 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001237 if len(args) > 1:
1238 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001239 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1240 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001241 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001242 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001243
1244
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001245@attr('epilog', """Example:
1246 gclient pack > patch.txt
1247 generate simple patch for configured client and dependences
1248""")
1249def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001250 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001251
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001252Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001253dependencies, and performs minimal postprocessing of the output. The
1254resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001255checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001256"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001257 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1258 help='override deps for the specified (comma-separated) '
1259 'platform(s); \'all\' will process all deps_os '
1260 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001261 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001262 client = GClient.LoadCurrentConfig(options)
1263 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001264 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001265 if options.verbose:
1266 # Print out the .gclient file. This is longer than if we just printed the
1267 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001268 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001269 return client.RunOnDeps('pack', args)
1270
1271
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001272def CMDstatus(parser, args):
1273 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001274 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1275 help='override deps for the specified (comma-separated) '
1276 'platform(s); \'all\' will process all deps_os '
1277 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001278 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001279 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001280 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001281 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001282 if options.verbose:
1283 # Print out the .gclient file. This is longer than if we just printed the
1284 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001285 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001286 return client.RunOnDeps('status', args)
1287
1288
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001289@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001290 gclient sync
1291 update files from SCM according to current configuration,
1292 *for modules which have changed since last update or sync*
1293 gclient sync --force
1294 update files from SCM according to current configuration, for
1295 all modules (useful for recovering files deleted from local copy)
1296 gclient sync --revision src@31000
1297 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001298""")
1299def CMDsync(parser, args):
1300 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001301 parser.add_option('-f', '--force', action='store_true',
1302 help='force update even for unchanged modules')
1303 parser.add_option('-n', '--nohooks', action='store_true',
1304 help='don\'t run hooks after the update is complete')
1305 parser.add_option('-r', '--revision', action='append',
1306 dest='revisions', metavar='REV', default=[],
1307 help='Enforces revision/hash for the solutions with the '
1308 'format src@rev. The src@ part is optional and can be '
1309 'skipped. -r can be used multiple times when .gclient '
1310 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001311 'if the src@ part is skipped. Note that specifying '
1312 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001313 parser.add_option('-t', '--transitive', action='store_true',
1314 help='When a revision is specified (in the DEPS file or '
1315 'with the command-line flag), transitively update '
1316 'the dependencies to the date of the given revision. '
1317 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001318 parser.add_option('-H', '--head', action='store_true',
1319 help='skips any safesync_urls specified in '
1320 'configured solutions and sync to head instead')
1321 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001322 help='Deletes from the working copy any dependencies that '
1323 'have been removed since the last sync, as long as '
1324 'there are no local modifications. When used with '
1325 '--force, such dependencies are removed even if they '
1326 'have local modifications. When used with --reset, '
1327 'all untracked directories are removed from the '
1328 'working copy, exclusing those which are explicitly '
1329 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001330 parser.add_option('-R', '--reset', action='store_true',
1331 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001332 parser.add_option('-M', '--merge', action='store_true',
1333 help='merge upstream changes instead of trying to '
1334 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001335 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1336 help='override deps for the specified (comma-separated) '
1337 'platform(s); \'all\' will process all deps_os '
1338 'references')
1339 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1340 help='Skip svn up whenever possible by requesting '
1341 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001342 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001343 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001344
1345 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001346 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001347
maruel@chromium.org307d1792010-05-31 20:03:13 +00001348 if options.revisions and options.head:
1349 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001350 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001351
1352 if options.verbose:
1353 # Print out the .gclient file. This is longer than if we just printed the
1354 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001355 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001356 return client.RunOnDeps('update', args)
1357
1358
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001359def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001360 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001361 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001362
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001363def CMDdiff(parser, args):
1364 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001365 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1366 help='override deps for the specified (comma-separated) '
1367 'platform(s); \'all\' will process all deps_os '
1368 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001369 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001370 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001371 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001372 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001373 if options.verbose:
1374 # Print out the .gclient file. This is longer than if we just printed the
1375 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001376 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001377 return client.RunOnDeps('diff', args)
1378
1379
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001380def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001381 """Revert all modifications in every dependencies.
1382
1383 That's the nuclear option to get back to a 'clean' state. It removes anything
1384 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001385 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1386 help='override deps for the specified (comma-separated) '
1387 'platform(s); \'all\' will process all deps_os '
1388 'references')
1389 parser.add_option('-n', '--nohooks', action='store_true',
1390 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001391 (options, args) = parser.parse_args(args)
1392 # --force is implied.
1393 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001394 options.reset = False
1395 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001396 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001397 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001398 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001399 return client.RunOnDeps('revert', args)
1400
1401
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001402def CMDrunhooks(parser, args):
1403 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001404 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1405 help='override deps for the specified (comma-separated) '
1406 'platform(s); \'all\' will process all deps_os '
1407 'references')
1408 parser.add_option('-f', '--force', action='store_true', default=True,
1409 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001410 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001411 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001412 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001413 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001414 if options.verbose:
1415 # Print out the .gclient file. This is longer than if we just printed the
1416 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001417 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001418 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001419 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001420 return client.RunOnDeps('runhooks', args)
1421
1422
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001423def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001424 """Output revision info mapping for the client and its dependencies.
1425
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001426 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001427 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001428 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1429 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001430 commit can change.
1431 """
1432 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1433 help='override deps for the specified (comma-separated) '
1434 'platform(s); \'all\' will process all deps_os '
1435 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001436 parser.add_option('-a', '--actual', action='store_true',
1437 help='gets the actual checked out revisions instead of the '
1438 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001439 parser.add_option('-s', '--snapshot', action='store_true',
1440 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001441 'version of all repositories to reproduce the tree, '
1442 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001443 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001444 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001445 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001446 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001447 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001448 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001449
1450
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001451def Command(name):
1452 return getattr(sys.modules[__name__], 'CMD' + name, None)
1453
1454
1455def CMDhelp(parser, args):
1456 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001457 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001458 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001459 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001460 parser.print_help()
1461 return 0
1462
1463
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001464def GenUsage(parser, command):
1465 """Modify an OptParse object with the function's documentation."""
1466 obj = Command(command)
1467 if command == 'help':
1468 command = '<command>'
1469 # OptParser.description prefer nicely non-formatted strings.
1470 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1471 usage = getattr(obj, 'usage', '')
1472 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1473 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001474
1475
maruel@chromium.org0895b752011-08-26 20:40:33 +00001476def Parser():
1477 """Returns the default parser."""
1478 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org41071612011-10-19 19:58:08 +00001479 # cygwin has issues with parallel sync
1480 jobs = 1 if sys.platform == 'cygwin' else 8
1481 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001482 help='Specify how many SCM commands can run in parallel; '
1483 'default=%default')
1484 parser.add_option('-v', '--verbose', action='count', default=0,
1485 help='Produces additional output for diagnostics. Can be '
1486 'used up to three times for more logging info.')
1487 parser.add_option('--gclientfile', dest='config_filename',
1488 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1489 help='Specify an alternate %default file')
1490 # Integrate standard options processing.
1491 old_parser = parser.parse_args
1492 def Parse(args):
1493 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001494 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1495 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001496 logging.basicConfig(level=level,
1497 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1498 options.entries_filename = options.config_filename + '_entries'
1499 if options.jobs < 1:
1500 parser.error('--jobs must be 1 or higher')
1501
1502 # These hacks need to die.
1503 if not hasattr(options, 'revisions'):
1504 # GClient.RunOnDeps expects it even if not applicable.
1505 options.revisions = []
1506 if not hasattr(options, 'head'):
1507 options.head = None
1508 if not hasattr(options, 'nohooks'):
1509 options.nohooks = True
1510 if not hasattr(options, 'deps_os'):
1511 options.deps_os = None
1512 if not hasattr(options, 'manually_grab_svn_rev'):
1513 options.manually_grab_svn_rev = None
1514 if not hasattr(options, 'force'):
1515 options.force = None
1516 return (options, args)
1517 parser.parse_args = Parse
1518 # We don't want wordwrapping in epilog (usually examples)
1519 parser.format_epilog = lambda _: parser.epilog or ''
1520 return parser
1521
1522
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001523def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001524 """Doesn't parse the arguments here, just find the right subcommand to
1525 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001526 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001527 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001528 '\nYour python version %s is unsupported, please upgrade.\n' %
1529 sys.version.split(' ', 1)[0])
1530 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001531 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001532 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001533 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1534 # operations. Python as a strong tendency to buffer sys.stdout.
1535 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001536 # Make stdout annotated with the thread ids.
1537 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001538 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001539 # Unused variable 'usage'
1540 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001541 def to_str(fn):
1542 return (
1543 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1544 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1545 cmds = (
1546 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1547 )
1548 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001549 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001550 if argv:
1551 command = Command(argv[0])
1552 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001553 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001554 GenUsage(parser, argv[0])
1555 return command(parser, argv[1:])
1556 # Not a known command. Default to help.
1557 GenUsage(parser, 'help')
1558 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001559 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001560 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001561 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001562
1563
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001564if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001565 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001566 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001567
1568# vim: ts=2:sw=2:tw=80:et: