blob: fce686a9574dfb4f28a5de36f3740227fbe5a91e [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
szager@google.comb9a78d32012-03-13 18:46:21 +0000607 @staticmethod
608 def GetHookAction(hook_dict, matching_file_list):
609 """Turns a parsed 'hook' dict into an executable command."""
610 logging.debug(hook_dict)
611 logging.debug(matching_file_list)
612 command = hook_dict['action'][:]
613 if command[0] == 'python':
614 # If the hook specified "python" as the first item, the action is a
615 # Python script. Run it by starting a new copy of the same
616 # interpreter.
617 command[0] = sys.executable
618 if '$matching_files' in command:
619 splice_index = command.index('$matching_files')
620 command[splice_index:splice_index + 1] = matching_file_list
621 return command
622
623 def GetHooks(self, options):
624 """Evaluates all hooks, and return them in a flat list.
625
626 RunOnDeps() must have been called before to load the DEPS.
627 """
628 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000629 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000630 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000631 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000632 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000633 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000634 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000635 # TODO(maruel): If the user is using git or git-svn, then we don't know
636 # what files have changed so we always run all hooks. It'd be nice to fix
637 # that.
638 if (options.force or
639 isinstance(self.parsed_url, self.FileImpl) or
640 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000641 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000642 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000643 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000644 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000645 # Run hooks on the basis of whether the files from the gclient operation
646 # match each hook's pattern.
647 for hook_dict in self.deps_hooks:
648 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000649 matching_file_list = [
650 f for f in self.file_list_and_children if pattern.search(f)
651 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000652 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000653 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000654 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000655 result.extend(s.GetHooks(options))
656 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000657
szager@google.comb9a78d32012-03-13 18:46:21 +0000658 def RunHooksRecursively(self, options):
659 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000660 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000661 for hook in self.GetHooks(options):
662 try:
663 gclient_utils.CheckCallAndFilterAndHeader(
664 hook, cwd=self.root.root_dir, always=True)
665 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
666 # Use a discrete exit status code of 2 to indicate that a hook action
667 # failed. Users of this script may wish to treat hook action failures
668 # differently from VC failures.
669 print >> sys.stderr, 'Error: %s' % str(e)
670 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000671
maruel@chromium.org0d812442010-08-10 12:41:08 +0000672 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000673 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000674 dependencies = self.dependencies
675 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000676 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000677 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000678 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000679 for i in d.subtree(include_all):
680 yield i
681
682 def depth_first_tree(self):
683 """Depth-first recursion including the root node."""
684 yield self
685 for i in self.dependencies:
686 for j in i.depth_first_tree():
687 if j.should_process:
688 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000689
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000690 @gclient_utils.lockedmethod
691 def add_dependency(self, new_dep):
692 self._dependencies.append(new_dep)
693
694 @gclient_utils.lockedmethod
695 def _mark_as_parsed(self, new_hooks):
696 self._deps_hooks.extend(new_hooks)
697 self._deps_parsed = True
698
maruel@chromium.org68988972011-09-20 14:11:42 +0000699 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000700 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000701 def dependencies(self):
702 return tuple(self._dependencies)
703
704 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000705 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000706 def deps_hooks(self):
707 return tuple(self._deps_hooks)
708
709 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000710 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000711 def parsed_url(self):
712 return self._parsed_url
713
714 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000715 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000716 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000717 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000718 return self._deps_parsed
719
720 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000721 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000722 def processed(self):
723 return self._processed
724
725 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000726 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000727 def hooks_ran(self):
728 return self._hooks_ran
729
730 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000731 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000732 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000733 return tuple(self._file_list)
734
735 @property
736 def file_list_and_children(self):
737 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000738 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000739 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000740 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000741
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000742 def __str__(self):
743 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000744 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000745 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000746 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000747 # First try the native property if it exists.
748 if hasattr(self, '_' + i):
749 value = getattr(self, '_' + i, False)
750 else:
751 value = getattr(self, i, False)
752 if value:
753 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000754
755 for d in self.dependencies:
756 out.extend([' ' + x for x in str(d).splitlines()])
757 out.append('')
758 return '\n'.join(out)
759
760 def __repr__(self):
761 return '%s: %s' % (self.name, self.url)
762
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000763 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000764 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000765 out = '%s(%s)' % (self.name, self.url)
766 i = self.parent
767 while i and i.name:
768 out = '%s(%s) -> %s' % (i.name, i.url, out)
769 i = i.parent
770 return out
771
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000772
773class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000774 """Object that represent a gclient checkout. A tree of Dependency(), one per
775 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000776
777 DEPS_OS_CHOICES = {
778 "win32": "win",
779 "win": "win",
780 "cygwin": "win",
781 "darwin": "mac",
782 "mac": "mac",
783 "unix": "unix",
784 "linux": "unix",
785 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000786 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000787 }
788
789 DEFAULT_CLIENT_FILE_TEXT = ("""\
790solutions = [
791 { "name" : "%(solution_name)s",
792 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000793 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000794 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000795 "custom_deps" : {
796 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000797 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000798 },
799]
800""")
801
802 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
803 { "name" : "%(solution_name)s",
804 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000805 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000806 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000807 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000808%(solution_deps)s },
809 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000810 },
811""")
812
813 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
814# Snapshot generated with gclient revinfo --snapshot
815solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000816%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000817""")
818
819 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000820 # Do not change previous behavior. Only solution level and immediate DEPS
821 # are processed.
822 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000823 Dependency.__init__(self, None, None, None, None, True, None, None,
824 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000825 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000826 if options.deps_os:
827 enforced_os = options.deps_os.split(',')
828 else:
829 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
830 if 'all' in enforced_os:
831 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000832 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000833 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000834 self.config_content = None
835
836 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000837 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000838 config_dict = {}
839 self.config_content = content
840 try:
841 exec(content, config_dict)
842 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000843 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000844
845 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000846 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000847 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000848 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000849 self, s['name'], s['url'],
850 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000851 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000852 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000853 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000854 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000855 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000856 except KeyError:
857 raise gclient_utils.Error('Invalid .gclient file. Solution is '
858 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000859 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
860 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000861
862 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000863 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000864 self._options.config_filename),
865 self.config_content)
866
867 @staticmethod
868 def LoadCurrentConfig(options):
869 """Searches for and loads a .gclient file relative to the current working
870 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000871 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000872 if not path:
873 return None
874 client = GClient(path, options)
875 client.SetConfig(gclient_utils.FileRead(
876 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000877
878 if (options.revisions and
879 len(client.dependencies) > 1 and
880 any('@' not in r for r in options.revisions)):
881 print >> sys.stderr, (
882 'You must specify the full solution name like --revision %s@%s\n'
883 'when you have multiple solutions setup in your .gclient file.\n'
884 'Other solutions present are: %s.') % (
885 client.dependencies[0].name,
886 options.revisions[0],
887 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000888 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000889
nsylvain@google.comefc80932011-05-31 21:27:56 +0000890 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000891 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000892 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
893 'solution_name': solution_name,
894 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000895 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000896 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000897 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000898 })
899
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000900 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000901 """Creates a .gclient_entries file to record the list of unique checkouts.
902
903 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000904 """
905 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
906 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000907 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000908 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000909 # Skip over File() dependencies as we can't version them.
910 if not isinstance(entry.parsed_url, self.FileImpl):
911 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
912 pprint.pformat(entry.parsed_url))
913 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000914 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000915 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000916 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000917
918 def _ReadEntries(self):
919 """Read the .gclient_entries file for the given client.
920
921 Returns:
922 A sequence of solution names, which will be empty if there is the
923 entries file hasn't been created yet.
924 """
925 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000926 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000927 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000928 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000929 try:
930 exec(gclient_utils.FileRead(filename), scope)
931 except SyntaxError, e:
932 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000933 return scope['entries']
934
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000935 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000936 """Checks for revision overrides."""
937 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000938 if self._options.head:
939 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000940 # Do not check safesync_url if one or more --revision flag is specified.
941 if not self._options.revisions:
942 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000943 if not s.managed:
944 self._options.revisions.append('%s@unmanaged' % s.name)
945 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000946 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000947 if not self._options.revisions:
948 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000949 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000950 index = 0
951 for revision in self._options.revisions:
952 if not '@' in revision:
953 # Support for --revision 123
954 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000955 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000956 if not sol in solutions_names:
957 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
958 print >> sys.stderr, ('Please fix your script, having invalid '
959 '--revision flags will soon considered an error.')
960 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000961 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000962 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000963 return revision_overrides
964
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000965 def _ApplySafeSyncRev(self, dep):
966 """Finds a valid revision from the content of the safesync_url and apply it
967 by appending revisions to the revision list. Throws if revision appears to
968 be invalid for the given |dep|."""
969 assert len(dep.safesync_url) > 0
970 handle = urllib.urlopen(dep.safesync_url)
971 rev = handle.read().strip()
972 handle.close()
973 if not rev:
974 raise gclient_utils.Error(
975 'It appears your safesync_url (%s) is not working properly\n'
976 '(as it returned an empty response). Check your config.' %
977 dep.safesync_url)
978 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
979 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
980 if self._options.verbose:
981 print('Using safesync_url revision: %s.\n' % safe_rev)
982 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
983
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000984 def RunOnDeps(self, command, args):
985 """Runs a command on each dependency in a client and its dependencies.
986
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000987 Args:
988 command: The command to use (e.g., 'status' or 'diff')
989 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000990 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000991 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000992 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000993 revision_overrides = {}
994 # It's unnecessary to check for revision overrides for 'recurse'.
995 # Save a few seconds by not calling _EnforceRevisions() in that case.
996 if command is not 'recurse':
997 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000998 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000999 # Disable progress for non-tty stdout.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001000 if (sys.stdout.isatty() and not self._options.verbose):
1001 if command in ('update', 'revert'):
1002 pm = Progress('Syncing projects', 1)
1003 elif command is 'recurse':
1004 pm = Progress(' '.join(args), 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001005 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001006 for s in self.dependencies:
1007 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001008 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001009
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001010 # Once all the dependencies have been processed, it's now safe to run the
1011 # hooks.
1012 if not self._options.nohooks:
1013 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001014
1015 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001016 # Notify the user if there is an orphaned entry in their working copy.
1017 # Only delete the directory if there are no changes in it, and
1018 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001019 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001020 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001021 if not prev_url:
1022 # entry must have been overridden via .gclient custom_deps
1023 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001024 # Fix path separator on Windows.
1025 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001026 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001027 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001028 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001029 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001030 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001031 scm.status(self._options, [], file_list)
1032 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001033 if (not self._options.delete_unversioned_trees or
1034 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001035 # There are modified files in this entry. Keep warning until
1036 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001037 print(('\nWARNING: \'%s\' is no longer part of this client. '
1038 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001039 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040 else:
1041 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001042 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001043 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001044 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001045 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001046 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001047 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001048
1049 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001050 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001051 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001052 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001053 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001054 for s in self.dependencies:
1055 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001056 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001057
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001058 def GetURLAndRev(dep):
1059 """Returns the revision-qualified SCM url for a Dependency."""
1060 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001061 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001062 if isinstance(dep.parsed_url, self.FileImpl):
1063 original_url = dep.parsed_url.file_location
1064 else:
1065 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001066 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001067 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001068 if not os.path.isdir(scm.checkout_path):
1069 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001070 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001071
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001072 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001073 new_gclient = ''
1074 # First level at .gclient
1075 for d in self.dependencies:
1076 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001077 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001078 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001079 for d in dep.dependencies:
1080 entries[d.name] = GetURLAndRev(d)
1081 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001082 GrabDeps(d)
1083 custom_deps = []
1084 for k in sorted(entries.keys()):
1085 if entries[k]:
1086 # Quotes aren't escaped...
1087 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1088 else:
1089 custom_deps.append(' \"%s\": None,\n' % k)
1090 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1091 'solution_name': d.name,
1092 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001093 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001094 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001095 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001096 'solution_deps': ''.join(custom_deps),
1097 }
1098 # Print the snapshot configuration file
1099 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001100 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001101 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001102 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001103 if self._options.actual:
1104 entries[d.name] = GetURLAndRev(d)
1105 else:
1106 entries[d.name] = d.parsed_url
1107 keys = sorted(entries.keys())
1108 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001109 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001110 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001111
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001112 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001113 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001114 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001115
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001116 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001117 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001118 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001119 return self._root_dir
1120
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001121 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001122 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001123 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001124 return self._enforced_os
1125
maruel@chromium.org68988972011-09-20 14:11:42 +00001126 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001127 def recursion_limit(self):
1128 """How recursive can each dependencies in DEPS file can load DEPS file."""
1129 return self._recursion_limit
1130
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001132#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001133
1134
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001135def CMDcleanup(parser, args):
1136 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001137
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001138Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001139"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001140 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1141 help='override deps for the specified (comma-separated) '
1142 'platform(s); \'all\' will process all deps_os '
1143 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001144 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001145 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001146 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001147 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001148 if options.verbose:
1149 # Print out the .gclient file. This is longer than if we just printed the
1150 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001151 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152 return client.RunOnDeps('cleanup', args)
1153
1154
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001155@attr('usage', '[command] [args ...]')
1156def CMDrecurse(parser, args):
1157 """Operates on all the entries.
1158
1159 Runs a shell command on all entries.
1160 """
1161 # Stop parsing at the first non-arg so that these go through to the command
1162 parser.disable_interspersed_args()
1163 parser.add_option('-s', '--scm', action='append', default=[],
1164 help='choose scm types to operate upon')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001165 parser.add_option('-i', '--ignore', action='store_true',
1166 help='continue processing in case of non zero return code')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001167 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001168 if not args:
1169 print >> sys.stderr, 'Need to supply a command!'
1170 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001171 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1172 if not root_and_entries:
1173 print >> sys.stderr, (
1174 'You need to run gclient sync at least once to use \'recurse\'.\n'
1175 'This is because .gclient_entries needs to exist and be up to date.')
1176 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001177
1178 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001179 scm_set = set()
1180 for scm in options.scm:
1181 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001182 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001183
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001184 options.nohooks = True
1185 client = GClient.LoadCurrentConfig(options)
1186 return client.RunOnDeps('recurse', args)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001187
1188
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001189@attr('usage', '[args ...]')
1190def CMDfetch(parser, args):
1191 """Fetches upstream commits for all modules.
1192
1193Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1194"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001195 (options, args) = parser.parse_args(args)
1196 args = ['-j%d' % options.jobs, '-s', 'git', 'git', 'fetch'] + args
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001197 return CMDrecurse(parser, args)
1198
1199
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001200@attr('usage', '[url] [safesync url]')
1201def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001202 """Create a .gclient file in the current directory.
1203
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001204This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001205top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001206modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001207provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001208URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001209"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001210 parser.add_option('--spec',
1211 help='create a gclient file containing the provided '
1212 'string. Due to Cygwin/Python brokenness, it '
1213 'probably can\'t contain any newlines.')
1214 parser.add_option('--name',
1215 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001216 parser.add_option('--deps-file', default='DEPS',
1217 help='overrides the default name for the DEPS file for the'
1218 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001219 parser.add_option('--unmanaged', action='store_true', default=False,
1220 help='overrides the default behavior to make it possible '
1221 'to have the main solution untouched by gclient '
1222 '(gclient will check out unmanaged dependencies but '
1223 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001224 parser.add_option('--git-deps', action='store_true',
1225 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001226 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001227 if ((options.spec and args) or len(args) > 2 or
1228 (not options.spec and not args)):
1229 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1230
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001231 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001232 if options.spec:
1233 client.SetConfig(options.spec)
1234 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001235 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001236 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001237 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001238 if name.endswith('.git'):
1239 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001240 else:
1241 # specify an alternate relpath for the given URL.
1242 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001243 deps_file = options.deps_file
1244 if options.git_deps:
1245 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001246 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001247 if len(args) > 1:
1248 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001249 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1250 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001251 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001252 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001253
1254
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001255@attr('epilog', """Example:
1256 gclient pack > patch.txt
1257 generate simple patch for configured client and dependences
1258""")
1259def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001260 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001261
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001262Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001263dependencies, and performs minimal postprocessing of the output. The
1264resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001265checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001266"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001267 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1268 help='override deps for the specified (comma-separated) '
1269 'platform(s); \'all\' will process all deps_os '
1270 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001271 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001272 client = GClient.LoadCurrentConfig(options)
1273 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001274 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001275 if options.verbose:
1276 # Print out the .gclient file. This is longer than if we just printed the
1277 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001278 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001279 return client.RunOnDeps('pack', args)
1280
1281
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001282def CMDstatus(parser, args):
1283 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001284 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1285 help='override deps for the specified (comma-separated) '
1286 'platform(s); \'all\' will process all deps_os '
1287 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001288 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001289 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001290 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001291 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001292 if options.verbose:
1293 # Print out the .gclient file. This is longer than if we just printed the
1294 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001295 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001296 return client.RunOnDeps('status', args)
1297
1298
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001299@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001300 gclient sync
1301 update files from SCM according to current configuration,
1302 *for modules which have changed since last update or sync*
1303 gclient sync --force
1304 update files from SCM according to current configuration, for
1305 all modules (useful for recovering files deleted from local copy)
1306 gclient sync --revision src@31000
1307 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001308""")
1309def CMDsync(parser, args):
1310 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001311 parser.add_option('-f', '--force', action='store_true',
1312 help='force update even for unchanged modules')
1313 parser.add_option('-n', '--nohooks', action='store_true',
1314 help='don\'t run hooks after the update is complete')
1315 parser.add_option('-r', '--revision', action='append',
1316 dest='revisions', metavar='REV', default=[],
1317 help='Enforces revision/hash for the solutions with the '
1318 'format src@rev. The src@ part is optional and can be '
1319 'skipped. -r can be used multiple times when .gclient '
1320 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001321 'if the src@ part is skipped. Note that specifying '
1322 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001323 parser.add_option('-t', '--transitive', action='store_true',
1324 help='When a revision is specified (in the DEPS file or '
1325 'with the command-line flag), transitively update '
1326 'the dependencies to the date of the given revision. '
1327 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001328 parser.add_option('-H', '--head', action='store_true',
1329 help='skips any safesync_urls specified in '
1330 'configured solutions and sync to head instead')
1331 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001332 help='Deletes from the working copy any dependencies that '
1333 'have been removed since the last sync, as long as '
1334 'there are no local modifications. When used with '
1335 '--force, such dependencies are removed even if they '
1336 'have local modifications. When used with --reset, '
1337 'all untracked directories are removed from the '
1338 'working copy, exclusing those which are explicitly '
1339 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001340 parser.add_option('-R', '--reset', action='store_true',
1341 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001342 parser.add_option('-M', '--merge', action='store_true',
1343 help='merge upstream changes instead of trying to '
1344 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001345 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1346 help='override deps for the specified (comma-separated) '
1347 'platform(s); \'all\' will process all deps_os '
1348 'references')
1349 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1350 help='Skip svn up whenever possible by requesting '
1351 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001352 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001353 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001354
1355 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001356 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001357
maruel@chromium.org307d1792010-05-31 20:03:13 +00001358 if options.revisions and options.head:
1359 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001360 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001361
1362 if options.verbose:
1363 # Print out the .gclient file. This is longer than if we just printed the
1364 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001365 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001366 return client.RunOnDeps('update', args)
1367
1368
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001369def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001370 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001371 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001372
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001373def CMDdiff(parser, args):
1374 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001375 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1376 help='override deps for the specified (comma-separated) '
1377 'platform(s); \'all\' will process all deps_os '
1378 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001379 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001380 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001381 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001382 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001383 if options.verbose:
1384 # Print out the .gclient file. This is longer than if we just printed the
1385 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001386 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001387 return client.RunOnDeps('diff', args)
1388
1389
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001390def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001391 """Revert all modifications in every dependencies.
1392
1393 That's the nuclear option to get back to a 'clean' state. It removes anything
1394 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001395 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1396 help='override deps for the specified (comma-separated) '
1397 'platform(s); \'all\' will process all deps_os '
1398 'references')
1399 parser.add_option('-n', '--nohooks', action='store_true',
1400 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001401 (options, args) = parser.parse_args(args)
1402 # --force is implied.
1403 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001404 options.reset = False
1405 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001406 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001407 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001408 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001409 return client.RunOnDeps('revert', args)
1410
1411
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001412def CMDrunhooks(parser, args):
1413 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001414 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1415 help='override deps for the specified (comma-separated) '
1416 'platform(s); \'all\' will process all deps_os '
1417 'references')
1418 parser.add_option('-f', '--force', action='store_true', default=True,
1419 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001420 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001421 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001422 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001423 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001424 if options.verbose:
1425 # Print out the .gclient file. This is longer than if we just printed the
1426 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001427 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001428 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001429 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001430 return client.RunOnDeps('runhooks', args)
1431
1432
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001433def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001434 """Output revision info mapping for the client and its dependencies.
1435
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001436 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001437 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001438 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1439 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001440 commit can change.
1441 """
1442 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1443 help='override deps for the specified (comma-separated) '
1444 'platform(s); \'all\' will process all deps_os '
1445 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001446 parser.add_option('-a', '--actual', action='store_true',
1447 help='gets the actual checked out revisions instead of the '
1448 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001449 parser.add_option('-s', '--snapshot', action='store_true',
1450 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001451 'version of all repositories to reproduce the tree, '
1452 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001453 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001454 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001455 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001456 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001457 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001458 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001459
1460
szager@google.comb9a78d32012-03-13 18:46:21 +00001461def CMDhookinfo(parser, args):
1462 """Output the hooks that would be run by `gclient runhooks`"""
1463 (options, args) = parser.parse_args(args)
1464 options.force = True
1465 client = GClient.LoadCurrentConfig(options)
1466 if not client:
1467 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1468 client.RunOnDeps(None, [])
1469 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1470 return 0
1471
1472
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001473def Command(name):
1474 return getattr(sys.modules[__name__], 'CMD' + name, None)
1475
1476
1477def CMDhelp(parser, args):
1478 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001479 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001480 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001481 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001482 parser.print_help()
1483 return 0
1484
1485
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001486def GenUsage(parser, command):
1487 """Modify an OptParse object with the function's documentation."""
1488 obj = Command(command)
1489 if command == 'help':
1490 command = '<command>'
1491 # OptParser.description prefer nicely non-formatted strings.
1492 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1493 usage = getattr(obj, 'usage', '')
1494 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1495 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001496
1497
maruel@chromium.org0895b752011-08-26 20:40:33 +00001498def Parser():
1499 """Returns the default parser."""
1500 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org41071612011-10-19 19:58:08 +00001501 # cygwin has issues with parallel sync
1502 jobs = 1 if sys.platform == 'cygwin' else 8
1503 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001504 help='Specify how many SCM commands can run in parallel; '
1505 'default=%default')
1506 parser.add_option('-v', '--verbose', action='count', default=0,
1507 help='Produces additional output for diagnostics. Can be '
1508 'used up to three times for more logging info.')
1509 parser.add_option('--gclientfile', dest='config_filename',
1510 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1511 help='Specify an alternate %default file')
1512 # Integrate standard options processing.
1513 old_parser = parser.parse_args
1514 def Parse(args):
1515 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001516 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1517 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001518 logging.basicConfig(level=level,
1519 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1520 options.entries_filename = options.config_filename + '_entries'
1521 if options.jobs < 1:
1522 parser.error('--jobs must be 1 or higher')
1523
1524 # These hacks need to die.
1525 if not hasattr(options, 'revisions'):
1526 # GClient.RunOnDeps expects it even if not applicable.
1527 options.revisions = []
1528 if not hasattr(options, 'head'):
1529 options.head = None
1530 if not hasattr(options, 'nohooks'):
1531 options.nohooks = True
1532 if not hasattr(options, 'deps_os'):
1533 options.deps_os = None
1534 if not hasattr(options, 'manually_grab_svn_rev'):
1535 options.manually_grab_svn_rev = None
1536 if not hasattr(options, 'force'):
1537 options.force = None
1538 return (options, args)
1539 parser.parse_args = Parse
1540 # We don't want wordwrapping in epilog (usually examples)
1541 parser.format_epilog = lambda _: parser.epilog or ''
1542 return parser
1543
1544
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001545def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001546 """Doesn't parse the arguments here, just find the right subcommand to
1547 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001548 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001549 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001550 '\nYour python version %s is unsupported, please upgrade.\n' %
1551 sys.version.split(' ', 1)[0])
1552 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001553 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001554 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001555 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1556 # operations. Python as a strong tendency to buffer sys.stdout.
1557 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001558 # Make stdout annotated with the thread ids.
1559 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001560 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001561 # Unused variable 'usage'
1562 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001563 def to_str(fn):
1564 return (
1565 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1566 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1567 cmds = (
1568 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1569 )
1570 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001571 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001572 if argv:
1573 command = Command(argv[0])
1574 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001575 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001576 GenUsage(parser, argv[0])
1577 return command(parser, argv[1:])
1578 # Not a known command. Default to help.
1579 GenUsage(parser, 'help')
1580 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001581 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001582 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001583 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001584
1585
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001586if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001587 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001588 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001589
1590# vim: ts=2:sw=2:tw=80:et: