blob: ed08e47f423cb96bbd6ba9c9ca2902cbc394cc62 [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
petermayo@chromium.orge79161a2013-07-09 14:40:37 +000013 module, as well as "custom_deps" to a map similar to the deps
14 section of the DEPS file below, as well as "custom_hooks" to
15 a list similar to the hooks sections of the DEPS file below.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000016 .gclient_entries : A cache constructed by 'update' command. Format is a
17 Python script defining 'entries', a list of the names
18 of all modules in the client
19 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
20 submodule name to a URL where it can be found (via one SCM)
21
22Hooks
23 .gclient and DEPS files may optionally contain a list named "hooks" to
24 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000025 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000026 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000027 be forced to run with the "runhooks" operation. If "sync" is run with
petermayo@chromium.orge79161a2013-07-09 14:40:37 +000028 --force, all known but not suppressed hooks will run regardless of the state
29 of the working copy.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000030
31 Each item in a "hooks" list is a dict, containing these two keys:
32 "pattern" The associated value is a string containing a regular
33 expression. When a file whose pathname matches the expression
34 is checked out, updated, or reverted, the hook's "action" will
35 run.
36 "action" A list describing a command to run along with its arguments, if
37 any. An action command will run at most one time per gclient
38 invocation, regardless of how many files matched the pattern.
39 The action is executed in the same directory as the .gclient
40 file. If the first item in the list is the string "python",
41 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000042 to run the command. If the list contains string "$matching_files"
43 it will be removed from the list and the list will be extended
44 by the list of matching files.
petermayo@chromium.orge79161a2013-07-09 14:40:37 +000045 "name" An optional string specifying the group to which a hook belongs
46 for overriding and organizing.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000047
48 Example:
49 hooks = [
50 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
51 "action": ["python", "image_indexer.py", "--all"]},
petermayo@chromium.orge79161a2013-07-09 14:40:37 +000052 { "pattern": ".",
53 "name": "gyp",
54 "action": ["python", "src/build/gyp_chromium"]},
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000055 ]
peter@chromium.org1efccc82012-04-27 16:34:38 +000056
57Specifying a target OS
58 An optional key named "target_os" may be added to a gclient file to specify
59 one or more additional operating systems that should be considered when
60 processing the deps_os dict of a DEPS file.
61
62 Example:
63 target_os = [ "android" ]
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +000064
65 If the "target_os_only" key is also present and true, then *only* the
66 operating systems listed in "target_os" will be used.
67
68 Example:
69 target_os = [ "ios" ]
70 target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071"""
72
maruel@chromium.org82798cb2012-02-23 18:16:12 +000073__version__ = "0.6.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000074
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000075import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000076import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077import optparse
78import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000079import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000080import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000081import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000083import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000084import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000086import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000087
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000088import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000089
maruel@chromium.org35625c72011-03-23 17:34:02 +000090import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000091import gclient_scm
92import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000093from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000094import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +000095from third_party import colorama
96# Import shortcut.
97from third_party.colorama import Fore
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000098
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000099
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000100def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000101 """Sets an attribute on a function."""
102 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000103 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000104 return fn
105 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000106
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000107
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000108## GClient implementation.
109
110
maruel@chromium.org116704f2010-06-11 17:34:38 +0000111class GClientKeywords(object):
112 class FromImpl(object):
113 """Used to implement the From() syntax."""
114
115 def __init__(self, module_name, sub_target_name=None):
116 """module_name is the dep module we want to include from. It can also be
117 the name of a subdirectory to include from.
118
119 sub_target_name is an optional parameter if the module name in the other
120 DEPS file is different. E.g., you might want to map src/net to net."""
121 self.module_name = module_name
122 self.sub_target_name = sub_target_name
123
124 def __str__(self):
125 return 'From(%s, %s)' % (repr(self.module_name),
126 repr(self.sub_target_name))
127
maruel@chromium.org116704f2010-06-11 17:34:38 +0000128 class FileImpl(object):
129 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000130 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000131
132 def __init__(self, file_location):
133 self.file_location = file_location
134
135 def __str__(self):
136 return 'File("%s")' % self.file_location
137
138 def GetPath(self):
139 return os.path.split(self.file_location)[0]
140
141 def GetFilename(self):
142 rev_tokens = self.file_location.split('@')
143 return os.path.split(rev_tokens[0])[1]
144
145 def GetRevision(self):
146 rev_tokens = self.file_location.split('@')
147 if len(rev_tokens) > 1:
148 return rev_tokens[1]
149 return None
150
151 class VarImpl(object):
152 def __init__(self, custom_vars, local_scope):
153 self._custom_vars = custom_vars
154 self._local_scope = local_scope
155
156 def Lookup(self, var_name):
157 """Implements the Var syntax."""
158 if var_name in self._custom_vars:
159 return self._custom_vars[var_name]
160 elif var_name in self._local_scope.get("vars", {}):
161 return self._local_scope["vars"][var_name]
162 raise gclient_utils.Error("Var is not defined: %s" % var_name)
163
164
maruel@chromium.org064186c2011-09-27 23:53:33 +0000165class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000166 """Immutable configuration settings."""
167 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000168 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000169 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000170 GClientKeywords.__init__(self)
171
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000172 # These are not mutable:
173 self._parent = parent
174 self._safesync_url = safesync_url
175 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000176 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000177 # 'managed' determines whether or not this dependency is synced/updated by
178 # gclient after gclient checks it out initially. The difference between
179 # 'managed' and 'should_process' is that the user specifies 'managed' via
180 # the --unmanaged command-line flag or a .gclient config, where
181 # 'should_process' is dynamically set by gclient if it goes over its
182 # recursion limit and controls gclient's behavior so it does not misbehave.
183 self._managed = managed
184 self._should_process = should_process
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000185 # This is a mutable value that overrides the normal recursion limit for this
186 # dependency. It is read from the actual DEPS file so cannot be set on
187 # class instantiation.
188 self.recursion_override = None
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000189 # This is a mutable value which has the list of 'target_os' OSes listed in
190 # the current deps file.
191 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000192
193 # These are only set in .gclient and not in DEPS files.
194 self._custom_vars = custom_vars or {}
195 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000196 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000197
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000198 # TODO(iannucci): Remove this when all masters are correctly substituting
199 # the new blink url.
200 if (self._custom_vars.get('webkit_trunk', '') ==
201 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000202 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
203 print 'Overwriting Var("webkit_trunk") with %s' % new_url
204 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000205
maruel@chromium.org064186c2011-09-27 23:53:33 +0000206 # Post process the url to remove trailing slashes.
207 if isinstance(self._url, basestring):
208 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
209 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000210 self._url = self._url.replace('/@', '@')
211 elif not isinstance(self._url,
212 (self.FromImpl, self.FileImpl, None.__class__)):
213 raise gclient_utils.Error(
214 ('dependency url must be either a string, None, '
215 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000216 # Make any deps_file path platform-appropriate.
217 for sep in ['/', '\\']:
218 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000219
220 @property
221 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000222 return self._deps_file
223
224 @property
225 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000226 return self._managed
227
228 @property
229 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000230 return self._parent
231
232 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000233 def root(self):
234 """Returns the root node, a GClient object."""
235 if not self.parent:
236 # This line is to signal pylint that it could be a GClient instance.
237 return self or GClient(None, None)
238 return self.parent.root
239
240 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000241 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 return self._safesync_url
243
244 @property
245 def should_process(self):
246 """True if this dependency should be processed, i.e. checked out."""
247 return self._should_process
248
249 @property
250 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000251 return self._custom_vars.copy()
252
253 @property
254 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000255 return self._custom_deps.copy()
256
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000258 def custom_hooks(self):
259 return self._custom_hooks[:]
260
261 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000262 def url(self):
263 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000264
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000265 @property
266 def recursion_limit(self):
267 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000268 if self.recursion_override is not None:
269 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000270 return max(self.parent.recursion_limit - 1, 0)
271
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000272 @property
273 def target_os(self):
274 if self.local_target_os is not None:
275 return tuple(set(self.local_target_os).union(self.parent.target_os))
276 else:
277 return self.parent.target_os
278
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000279 def get_custom_deps(self, name, url):
280 """Returns a custom deps if applicable."""
281 if self.parent:
282 url = self.parent.get_custom_deps(name, url)
283 # None is a valid return value to disable a dependency.
284 return self.custom_deps.get(name, url)
285
maruel@chromium.org064186c2011-09-27 23:53:33 +0000286
287class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000288 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000289
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000290 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000291 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000292 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000293 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000294 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000295 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000296
297 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000298 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000299
300 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000301 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000302 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000303 # A cache of the files affected by the current operation, necessary for
304 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000305 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000306 # If it is not set to True, the dependency wasn't processed for its child
307 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000308 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000309 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000310 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000311 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000312 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000313 # This is the scm used to checkout self.url. It may be used by dependencies
314 # to get the datetime of the revision we checked out.
315 self._used_scm = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000316
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000317 if not self.name and self.parent:
318 raise gclient_utils.Error('Dependency without name')
319
maruel@chromium.org470b5432011-10-11 18:18:19 +0000320 @property
321 def requirements(self):
322 """Calculate the list of requirements."""
323 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000324 # self.parent is implicitly a requirement. This will be recursive by
325 # definition.
326 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000327 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000328
329 # For a tree with at least 2 levels*, the leaf node needs to depend
330 # on the level higher up in an orderly way.
331 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
332 # thus unsorted, while the .gclient format is a list thus sorted.
333 #
334 # * _recursion_limit is hard coded 2 and there is no hope to change this
335 # value.
336 #
337 # Interestingly enough, the following condition only works in the case we
338 # want: self is a 2nd level node. 3nd level node wouldn't need this since
339 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000340 if self.parent and self.parent.parent and not self.parent.parent.parent:
341 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000342
343 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000344 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000345
maruel@chromium.org470b5432011-10-11 18:18:19 +0000346 if self.name:
347 requirements |= set(
348 obj.name for obj in self.root.subtree(False)
349 if (obj is not self
350 and obj.name and
351 self.name.startswith(posixpath.join(obj.name, ''))))
352 requirements = tuple(sorted(requirements))
353 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
354 return requirements
355
356 def verify_validity(self):
357 """Verifies that this Dependency is fine to add as a child of another one.
358
359 Returns True if this entry should be added, False if it is a duplicate of
360 another entry.
361 """
362 logging.info('Dependency(%s).verify_validity()' % self.name)
363 if self.name in [s.name for s in self.parent.dependencies]:
364 raise gclient_utils.Error(
365 'The same name "%s" appears multiple times in the deps section' %
366 self.name)
367 if not self.should_process:
368 # Return early, no need to set requirements.
369 return True
370
371 # This require a full tree traversal with locks.
372 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
373 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000374 self_url = self.LateOverride(self.url)
375 sibling_url = sibling.LateOverride(sibling.url)
376 # Allow to have only one to be None or ''.
377 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000378 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000379 ('Dependency %s specified more than once:\n'
380 ' %s [%s]\n'
381 'vs\n'
382 ' %s [%s]') % (
383 self.name,
384 sibling.hierarchy(),
385 sibling_url,
386 self.hierarchy(),
387 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000388 # In theory we could keep it as a shadow of the other one. In
389 # practice, simply ignore it.
390 logging.warn('Won\'t process duplicate dependency %s' % sibling)
391 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000392 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000393
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000394 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000395 """Resolves the parsed url from url.
396
397 Manages From() keyword accordingly. Do not touch self.parsed_url nor
398 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000399 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000400 parsed_url = self.get_custom_deps(self.name, url)
401 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000402 logging.info(
403 'Dependency(%s).LateOverride(%s) -> %s' %
404 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000405 return parsed_url
406
407 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000408 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000409 ref = [
410 dep for dep in self.root.subtree(True) if url.module_name == dep.name
411 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000412 if not ref:
413 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
414 url.module_name, ref))
415 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000416 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000417 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000418 found_deps = [d for d in ref.dependencies if d.name == sub_target]
419 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000420 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000421 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
422 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000423 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000424
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000425 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000426 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000427 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000428 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000429 'Dependency(%s).LateOverride(%s) -> %s (From)' %
430 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000431 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000432
433 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000434 parsed_url = urlparse.urlparse(url)
435 if not parsed_url[0]:
436 # A relative url. Fetch the real base.
437 path = parsed_url[2]
438 if not path.startswith('/'):
439 raise gclient_utils.Error(
440 'relative DEPS entry \'%s\' must begin with a slash' % url)
441 # Create a scm just to query the full url.
442 parent_url = self.parent.parsed_url
443 if isinstance(parent_url, self.FileImpl):
444 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000445 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000446 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000447 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000448 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000449 logging.info(
450 'Dependency(%s).LateOverride(%s) -> %s' %
451 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000452 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000453
454 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000455 logging.info(
456 'Dependency(%s).LateOverride(%s) -> %s (File)' %
457 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000458 return url
459
460 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000461 logging.info(
462 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000463 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000464
465 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000466
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000467 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000468 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000469 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000470 assert not self.dependencies
471 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000472 local_scope = {}
473 var = self.VarImpl(self.custom_vars, local_scope)
474 global_scope = {
475 'File': self.FileImpl,
476 'From': self.FromImpl,
477 'Var': var.Lookup,
478 'deps_os': {},
479 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000480 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000481 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000482 logging.info(
483 'ParseDepsFile(%s): No %s file found at %s' % (
484 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000485 else:
486 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000487 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000488 # Eval the content.
489 try:
490 exec(deps_content, global_scope, local_scope)
491 except SyntaxError, e:
492 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000493 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000494 if 'recursion' in local_scope:
495 self.recursion_override = local_scope.get('recursion')
496 logging.warning(
497 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000498 # If present, save 'target_os' in the local_target_os property.
499 if 'target_os' in local_scope:
500 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000501 # load os specific dependencies if defined. these dependencies may
502 # override or extend the values defined by the 'deps' member.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000503 target_os_deps = {}
maruel@chromium.org271375b2010-06-23 19:17:38 +0000504 if 'deps_os' in local_scope:
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000505 for deps_os_key in self.target_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000506 os_deps = local_scope['deps_os'].get(deps_os_key, {})
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000507 if len(self.target_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000508 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000509 # platform, so we collect the broadest set of dependencies
510 # available. We may end up with the wrong revision of something for
511 # our platform, but this is the best we can do.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000512 target_os_deps.update(
513 [x for x in os_deps.items() if not x[0] in target_os_deps])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000514 else:
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000515 target_os_deps.update(os_deps)
516
517 # deps_os overrides paths from deps
518 deps.update(target_os_deps)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000519
maruel@chromium.org271375b2010-06-23 19:17:38 +0000520 # If a line is in custom_deps, but not in the solution, we want to append
521 # this line to the solution.
522 for d in self.custom_deps:
523 if d not in deps:
524 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000525
526 # If use_relative_paths is set in the DEPS file, regenerate
527 # the dictionary using paths relative to the directory containing
528 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000529 use_relative_paths = local_scope.get('use_relative_paths', False)
530 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000531 rel_deps = {}
532 for d, url in deps.items():
533 # normpath is required to allow DEPS to use .. in their
534 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000535 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
536 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000537
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000538 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000539 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000540 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000541 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000542 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000543 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000544 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000545 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000546
547 # override named sets of hooks by the custom hooks
548 hooks_to_run = []
549 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
550 for hook in local_scope.get('hooks', []):
551 if hook.get('name', '') not in hook_names_to_suppress:
552 hooks_to_run.append(hook)
553
554 # add the replacements and any additions
555 for hook in self.custom_hooks:
556 if 'action' in hook:
557 hooks_to_run.append(hook)
558
559 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000560 logging.info('ParseDepsFile(%s) done' % self.name)
561
562 def add_dependencies_and_close(self, deps_to_add, hooks):
563 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000564 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000565 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000566 self.add_dependency(dep)
567 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000568
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000569 def maybeGetParentRevision(
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000570 self, command, options, parsed_url, parent_name, revision_overrides):
571 """Uses revision/timestamp of parent if no explicit revision was specified.
572
573 If we are performing an update and --transitive is set, use
574 - the parent's revision if 'self.url' is in the same repository
575 - the parent's timestamp otherwise
576 to update 'self.url'. The used revision/timestamp will be set in
577 'options.revision'.
578 If we have an explicit revision do nothing.
579 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000580 if command == 'update' and options.transitive and not options.revision:
581 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
582 if not revision:
583 options.revision = revision_overrides.get(parent_name)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000584 if (options.revision and
585 not gclient_utils.IsDateRevision(options.revision)):
586 assert self.parent and self.parent.used_scm
587 # If this dependency is in the same repository as parent it's url will
588 # start with a slash. If so we take the parent revision instead of
589 # it's timestamp.
590 # (The timestamps of commits in google code are broken -- which can
591 # result in dependencies to be checked out at the wrong revision)
592 if self.url.startswith('/'):
593 if options.verbose:
594 print('Using parent\'s revision %s since we are in the same '
595 'repository.' % options.revision)
596 else:
597 parent_revision_date = self.parent.used_scm.GetRevisionDate(
598 options.revision)
599 options.revision = gclient_utils.MakeDateRevision(
600 parent_revision_date)
601 if options.verbose:
602 print('Using parent\'s revision date %s since we are in a '
603 'different repository.' % options.revision)
604 revision_overrides[self.name] = options.revision
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000605
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000606 # Arguments number differs from overridden method
607 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000608 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000609 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000610 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000611 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000612 if not self.should_process:
613 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000614 # When running runhooks, there's no need to consult the SCM.
615 # All known hooks are expected to run unconditionally regardless of working
616 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000617 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000618 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000619 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000620 if run_scm and parsed_url:
621 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000622 # Special support for single-file checkout.
623 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000624 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
625 # pylint: disable=E1103
626 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000627 self._used_scm = gclient_scm.SVNWrapper(
628 parsed_url.GetPath(), self.root.root_dir, self.name)
629 self._used_scm.RunCommand('updatesingle',
630 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000631 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000632 # Create a shallow copy to mutate revision.
633 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000634 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000635 self.maybeGetParentRevision(
636 command, options, parsed_url, self.parent.name, revision_overrides)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000637 self._used_scm = gclient_scm.CreateSCM(
638 parsed_url, self.root.root_dir, self.name)
639 self._used_scm.RunCommand(command, options, args, file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000640 if file_list:
641 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000642
643 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
644 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000645 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000646 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000647 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000648 continue
649 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000650 [self.root.root_dir.lower(), file_list[i].lower()])
651 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000652 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000653 while file_list[i].startswith(('\\', '/')):
654 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000655
656 # Always parse the DEPS file.
657 self.ParseDepsFile()
658
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000659 self._run_is_done(file_list or [], parsed_url)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000660
661 if self.recursion_limit:
662 # Parse the dependencies of this dependency.
663 for s in self.dependencies:
664 work_queue.enqueue(s)
665
666 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000667 if not isinstance(parsed_url, self.FileImpl):
668 # Skip file only checkout.
669 scm = gclient_scm.GetScmName(parsed_url)
670 if not options.scm or scm in options.scm:
671 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
672 # Pass in the SCM type as an env variable
673 env = os.environ.copy()
674 if scm:
675 env['GCLIENT_SCM'] = scm
676 if parsed_url:
677 env['GCLIENT_URL'] = parsed_url
ilevy@chromium.org37116242012-11-28 01:32:48 +0000678 env['GCLIENT_DEP_PATH'] = self.name
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000679 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000680 print_stdout = False
681 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000682 """Git-specific path marshaling. It is optimized for git-grep."""
683
684 def mod_path(git_pathspec):
685 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
686 modified_path = os.path.join(self.name, match.group(2))
687 branch = match.group(1) or ''
688 return '%s%s' % (branch, modified_path)
689
690 match = re.match('^Binary file ([^\0]+) matches$', line)
691 if match:
692 print 'Binary file %s matches' % mod_path(match.group(1))
693 return
694
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000695 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000696 if len(items) == 2 and items[1]:
697 print '%s : %s' % (mod_path(items[0]), items[1])
698 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000699 # Multiple null bytes or a single trailing null byte indicate
700 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000701 print '\n'.join(mod_path(path) for path in items if path)
702 else:
703 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000704 else:
705 print_stdout = True
706 filter_fn = None
707
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000708 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000709 try:
710 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000711 args, cwd=cwd, env=env, print_stdout=print_stdout,
712 filter_fn=filter_fn,
713 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000714 except subprocess2.CalledProcessError:
715 if not options.ignore:
716 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000717 else:
718 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000719
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000720
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000721 @gclient_utils.lockedmethod
722 def _run_is_done(self, file_list, parsed_url):
723 # Both these are kept for hooks that are run as a separate tree traversal.
724 self._file_list = file_list
725 self._parsed_url = parsed_url
726 self._processed = True
727
szager@google.comb9a78d32012-03-13 18:46:21 +0000728 @staticmethod
729 def GetHookAction(hook_dict, matching_file_list):
730 """Turns a parsed 'hook' dict into an executable command."""
731 logging.debug(hook_dict)
732 logging.debug(matching_file_list)
733 command = hook_dict['action'][:]
734 if command[0] == 'python':
735 # If the hook specified "python" as the first item, the action is a
736 # Python script. Run it by starting a new copy of the same
737 # interpreter.
738 command[0] = sys.executable
739 if '$matching_files' in command:
740 splice_index = command.index('$matching_files')
741 command[splice_index:splice_index + 1] = matching_file_list
742 return command
743
744 def GetHooks(self, options):
745 """Evaluates all hooks, and return them in a flat list.
746
747 RunOnDeps() must have been called before to load the DEPS.
748 """
749 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000750 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000751 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000752 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000753 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000754 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000755 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000756 # TODO(maruel): If the user is using git or git-svn, then we don't know
757 # what files have changed so we always run all hooks. It'd be nice to fix
758 # that.
759 if (options.force or
760 isinstance(self.parsed_url, self.FileImpl) or
761 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000762 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000763 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000764 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000765 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000766 # Run hooks on the basis of whether the files from the gclient operation
767 # match each hook's pattern.
768 for hook_dict in self.deps_hooks:
769 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000770 matching_file_list = [
771 f for f in self.file_list_and_children if pattern.search(f)
772 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000773 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000774 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000775 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000776 result.extend(s.GetHooks(options))
777 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000778
szager@google.comb9a78d32012-03-13 18:46:21 +0000779 def RunHooksRecursively(self, options):
780 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000781 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000782 for hook in self.GetHooks(options):
783 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000784 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000785 gclient_utils.CheckCallAndFilterAndHeader(
786 hook, cwd=self.root.root_dir, always=True)
787 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
788 # Use a discrete exit status code of 2 to indicate that a hook action
789 # failed. Users of this script may wish to treat hook action failures
790 # differently from VC failures.
791 print >> sys.stderr, 'Error: %s' % str(e)
792 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000793 finally:
794 elapsed_time = time.time() - start_time
795 if elapsed_time > 10:
796 print "Hook '%s' took %.2f secs" % (
797 gclient_utils.CommandToStr(hook), elapsed_time)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000798
maruel@chromium.org0d812442010-08-10 12:41:08 +0000799 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000800 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000801 dependencies = self.dependencies
802 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000803 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000804 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000805 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000806 for i in d.subtree(include_all):
807 yield i
808
809 def depth_first_tree(self):
810 """Depth-first recursion including the root node."""
811 yield self
812 for i in self.dependencies:
813 for j in i.depth_first_tree():
814 if j.should_process:
815 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000816
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000817 @gclient_utils.lockedmethod
818 def add_dependency(self, new_dep):
819 self._dependencies.append(new_dep)
820
821 @gclient_utils.lockedmethod
822 def _mark_as_parsed(self, new_hooks):
823 self._deps_hooks.extend(new_hooks)
824 self._deps_parsed = True
825
maruel@chromium.org68988972011-09-20 14:11:42 +0000826 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000827 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000828 def dependencies(self):
829 return tuple(self._dependencies)
830
831 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000832 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000833 def deps_hooks(self):
834 return tuple(self._deps_hooks)
835
836 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000837 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000838 def parsed_url(self):
839 return self._parsed_url
840
841 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000842 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000843 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000844 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000845 return self._deps_parsed
846
847 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000848 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000849 def processed(self):
850 return self._processed
851
852 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000853 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000854 def hooks_ran(self):
855 return self._hooks_ran
856
857 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000858 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000859 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000860 return tuple(self._file_list)
861
862 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000863 def used_scm(self):
864 """SCMWrapper instance for this dependency or None if not processed yet."""
865 return self._used_scm
866
867 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000868 def file_list_and_children(self):
869 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000870 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000871 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000872 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000873
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000874 def __str__(self):
875 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000876 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000877 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000878 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000879 # First try the native property if it exists.
880 if hasattr(self, '_' + i):
881 value = getattr(self, '_' + i, False)
882 else:
883 value = getattr(self, i, False)
884 if value:
885 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000886
887 for d in self.dependencies:
888 out.extend([' ' + x for x in str(d).splitlines()])
889 out.append('')
890 return '\n'.join(out)
891
892 def __repr__(self):
893 return '%s: %s' % (self.name, self.url)
894
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000895 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000896 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000897 out = '%s(%s)' % (self.name, self.url)
898 i = self.parent
899 while i and i.name:
900 out = '%s(%s) -> %s' % (i.name, i.url, out)
901 i = i.parent
902 return out
903
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000904
905class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000906 """Object that represent a gclient checkout. A tree of Dependency(), one per
907 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000908
909 DEPS_OS_CHOICES = {
910 "win32": "win",
911 "win": "win",
912 "cygwin": "win",
913 "darwin": "mac",
914 "mac": "mac",
915 "unix": "unix",
916 "linux": "unix",
917 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000918 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000919 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000920 }
921
922 DEFAULT_CLIENT_FILE_TEXT = ("""\
923solutions = [
924 { "name" : "%(solution_name)s",
925 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000926 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000927 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000928 "custom_deps" : {
929 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000930 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000931 },
932]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000933cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000934""")
935
936 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
937 { "name" : "%(solution_name)s",
938 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000939 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000940 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000941 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000942%(solution_deps)s },
943 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000944 },
945""")
946
947 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
948# Snapshot generated with gclient revinfo --snapshot
949solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000950%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000951""")
952
953 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000954 # Do not change previous behavior. Only solution level and immediate DEPS
955 # are processed.
956 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000957 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000958 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000959 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000960 if options.deps_os:
961 enforced_os = options.deps_os.split(',')
962 else:
963 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
964 if 'all' in enforced_os:
965 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000966 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000967 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000968 self.config_content = None
969
970 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000971 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000972 config_dict = {}
973 self.config_content = content
974 try:
975 exec(content, config_dict)
976 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000977 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000978
peter@chromium.org1efccc82012-04-27 16:34:38 +0000979 # Append any target OS that is not already being enforced to the tuple.
980 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000981 if config_dict.get('target_os_only', False):
982 self._enforced_os = tuple(set(target_os))
983 else:
984 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
985
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000986 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
987
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000988 if not target_os and config_dict.get('target_os_only', False):
989 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
990 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +0000991
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000992 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000993 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000994 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000995 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000996 self, s['name'], s['url'],
997 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000998 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000999 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001000 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001001 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001002 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001003 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001004 except KeyError:
1005 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1006 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001007 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1008 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001009
1010 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001011 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001012 self._options.config_filename),
1013 self.config_content)
1014
1015 @staticmethod
1016 def LoadCurrentConfig(options):
1017 """Searches for and loads a .gclient file relative to the current working
1018 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001019 if options.spec:
1020 client = GClient('.', options)
1021 client.SetConfig(options.spec)
1022 else:
1023 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1024 if not path:
1025 return None
1026 client = GClient(path, options)
1027 client.SetConfig(gclient_utils.FileRead(
1028 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001029
1030 if (options.revisions and
1031 len(client.dependencies) > 1 and
1032 any('@' not in r for r in options.revisions)):
1033 print >> sys.stderr, (
1034 'You must specify the full solution name like --revision %s@%s\n'
1035 'when you have multiple solutions setup in your .gclient file.\n'
1036 'Other solutions present are: %s.') % (
1037 client.dependencies[0].name,
1038 options.revisions[0],
1039 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001040 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001041
nsylvain@google.comefc80932011-05-31 21:27:56 +00001042 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001043 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001044 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1045 'solution_name': solution_name,
1046 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001047 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001048 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001049 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001050 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001051 })
1052
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001053 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001054 """Creates a .gclient_entries file to record the list of unique checkouts.
1055
1056 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001057 """
1058 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1059 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001060 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001061 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001062 # Skip over File() dependencies as we can't version them.
1063 if not isinstance(entry.parsed_url, self.FileImpl):
1064 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1065 pprint.pformat(entry.parsed_url))
1066 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001067 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001068 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001069 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001070
1071 def _ReadEntries(self):
1072 """Read the .gclient_entries file for the given client.
1073
1074 Returns:
1075 A sequence of solution names, which will be empty if there is the
1076 entries file hasn't been created yet.
1077 """
1078 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001079 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001080 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001081 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001082 try:
1083 exec(gclient_utils.FileRead(filename), scope)
1084 except SyntaxError, e:
1085 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001086 return scope['entries']
1087
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001088 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001089 """Checks for revision overrides."""
1090 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001091 if self._options.head:
1092 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001093 # Do not check safesync_url if one or more --revision flag is specified.
1094 if not self._options.revisions:
1095 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001096 if not s.managed:
1097 self._options.revisions.append('%s@unmanaged' % s.name)
1098 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001099 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001100 if not self._options.revisions:
1101 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001102 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001103 index = 0
1104 for revision in self._options.revisions:
1105 if not '@' in revision:
1106 # Support for --revision 123
1107 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001108 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001109 if not sol in solutions_names:
1110 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1111 print >> sys.stderr, ('Please fix your script, having invalid '
1112 '--revision flags will soon considered an error.')
1113 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001114 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001115 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001116 return revision_overrides
1117
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001118 def _ApplySafeSyncRev(self, dep):
1119 """Finds a valid revision from the content of the safesync_url and apply it
1120 by appending revisions to the revision list. Throws if revision appears to
1121 be invalid for the given |dep|."""
1122 assert len(dep.safesync_url) > 0
1123 handle = urllib.urlopen(dep.safesync_url)
1124 rev = handle.read().strip()
1125 handle.close()
1126 if not rev:
1127 raise gclient_utils.Error(
1128 'It appears your safesync_url (%s) is not working properly\n'
1129 '(as it returned an empty response). Check your config.' %
1130 dep.safesync_url)
1131 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001132 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001133 if self._options.verbose:
1134 print('Using safesync_url revision: %s.\n' % safe_rev)
1135 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1136
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001137 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001138 """Runs a command on each dependency in a client and its dependencies.
1139
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001140 Args:
1141 command: The command to use (e.g., 'status' or 'diff')
1142 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001143 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001144 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001145 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001146 revision_overrides = {}
1147 # It's unnecessary to check for revision overrides for 'recurse'.
1148 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001149 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001150 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001151 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001152 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001153 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001154 if command in ('update', 'revert'):
1155 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001156 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001157 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001158 work_queue = gclient_utils.ExecutionQueue(
1159 self._options.jobs, pm, ignore_requirements=ignore_requirements)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001160 for s in self.dependencies:
1161 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001162 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001163
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001164 # Once all the dependencies have been processed, it's now safe to run the
1165 # hooks.
1166 if not self._options.nohooks:
1167 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001168
1169 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001170 # Notify the user if there is an orphaned entry in their working copy.
1171 # Only delete the directory if there are no changes in it, and
1172 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001173 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001174 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1175 for e in entries]
1176
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001177 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001178 if not prev_url:
1179 # entry must have been overridden via .gclient custom_deps
1180 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001181 # Fix path separator on Windows.
1182 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001183 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001184
1185 def _IsParentOfAny(parent, path_list):
1186 parent_plus_slash = parent + '/'
1187 return any(
1188 path[:len(parent_plus_slash)] == parent_plus_slash
1189 for path in path_list)
1190
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001191 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001192 if (entry not in entries and
1193 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001194 os.path.exists(e_dir)):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001195 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001196
1197 # Check to see if this directory is now part of a higher-up checkout.
1198 if scm.GetCheckoutRoot() in full_entries:
1199 logging.info('%s is part of a higher level checkout, not '
1200 'removing.', scm.GetCheckoutRoot())
1201 continue
1202
1203 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001204 scm.status(self._options, [], file_list)
1205 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001206 if (not self._options.delete_unversioned_trees or
1207 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001208 # There are modified files in this entry. Keep warning until
1209 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001210 print(('\nWARNING: \'%s\' is no longer part of this client. '
1211 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001212 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001213 else:
1214 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001215 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001216 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001217 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001218 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001219 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001220 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001221
1222 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001223 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001224 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001225 # Load all the settings.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001226 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None, False)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001227 for s in self.dependencies:
1228 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001229 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001230
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001231 def GetURLAndRev(dep):
1232 """Returns the revision-qualified SCM url for a Dependency."""
1233 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001234 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001235 if isinstance(dep.parsed_url, self.FileImpl):
1236 original_url = dep.parsed_url.file_location
1237 else:
1238 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001239 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001240 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001241 if not os.path.isdir(scm.checkout_path):
1242 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001243 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001244
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001245 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001246 new_gclient = ''
1247 # First level at .gclient
1248 for d in self.dependencies:
1249 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001250 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001251 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001252 for d in dep.dependencies:
1253 entries[d.name] = GetURLAndRev(d)
1254 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001255 GrabDeps(d)
1256 custom_deps = []
1257 for k in sorted(entries.keys()):
1258 if entries[k]:
1259 # Quotes aren't escaped...
1260 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1261 else:
1262 custom_deps.append(' \"%s\": None,\n' % k)
1263 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1264 'solution_name': d.name,
1265 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001266 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001267 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001268 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001269 'solution_deps': ''.join(custom_deps),
1270 }
1271 # Print the snapshot configuration file
1272 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001273 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001274 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001275 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001276 if self._options.actual:
1277 entries[d.name] = GetURLAndRev(d)
1278 else:
1279 entries[d.name] = d.parsed_url
1280 keys = sorted(entries.keys())
1281 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001282 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001283 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001284
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001285 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001286 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001287 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001288
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001289 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001290 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001291 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001292 return self._root_dir
1293
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001294 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001295 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001296 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001297 return self._enforced_os
1298
maruel@chromium.org68988972011-09-20 14:11:42 +00001299 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001300 def recursion_limit(self):
1301 """How recursive can each dependencies in DEPS file can load DEPS file."""
1302 return self._recursion_limit
1303
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001304 @property
1305 def target_os(self):
1306 return self._enforced_os
1307
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001308
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001309#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001310
1311
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001312def CMDcleanup(parser, args):
1313 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001314
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001315Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001316"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001317 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1318 help='override deps for the specified (comma-separated) '
1319 'platform(s); \'all\' will process all deps_os '
1320 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001321 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001322 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001323 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001324 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001325 if options.verbose:
1326 # Print out the .gclient file. This is longer than if we just printed the
1327 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001328 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001329 return client.RunOnDeps('cleanup', args)
1330
1331
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001332@attr('usage', '[command] [args ...]')
1333def CMDrecurse(parser, args):
1334 """Operates on all the entries.
1335
1336 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001337 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1338 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001339 """
1340 # Stop parsing at the first non-arg so that these go through to the command
1341 parser.disable_interspersed_args()
1342 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001343 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001344 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001345 help='Ignore non-zero return codes from subcommands.')
1346 parser.add_option('--prepend-dir', action='store_true',
1347 help='Prepend relative dir for use with git <cmd> --null.')
1348 parser.add_option('--no-progress', action='store_true',
1349 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001350 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001351 if not args:
1352 print >> sys.stderr, 'Need to supply a command!'
1353 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001354 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1355 if not root_and_entries:
1356 print >> sys.stderr, (
1357 'You need to run gclient sync at least once to use \'recurse\'.\n'
1358 'This is because .gclient_entries needs to exist and be up to date.')
1359 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001360
1361 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001362 scm_set = set()
1363 for scm in options.scm:
1364 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001365 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001366
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001367 options.nohooks = True
1368 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001369 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1370 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001371
1372
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001373@attr('usage', '[args ...]')
1374def CMDfetch(parser, args):
1375 """Fetches upstream commits for all modules.
1376
1377Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1378"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001379 (options, args) = parser.parse_args(args)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001380 return CMDrecurse(Parser(), [
1381 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1382
1383
1384def CMDgrep(parser, args):
1385 """Greps through git repos managed by gclient.
1386
1387Runs 'git grep [args...]' for each module.
1388"""
1389
1390 # We can't use optparse because it will try to parse arguments sent
1391 # to git grep and throw an error. :-(
1392 if not args or re.match('(-h|--help)$', args[0]):
1393 print >> sys.stderr, (
1394 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1395 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1396 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1397 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1398 ' end of your query.'
1399 )
1400 return 1
1401
1402 jobs_arg = ['--jobs=1']
1403 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1404 jobs_arg, args = args[:1], args[1:]
1405 elif re.match(r'(-j|--jobs)$', args[0]):
1406 jobs_arg, args = args[:2], args[2:]
1407
1408 return CMDrecurse(
1409 parser,
1410 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1411 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001412
1413
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001414@attr('usage', '[url] [safesync url]')
1415def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001416 """Create a .gclient file in the current directory.
1417
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001418This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001419top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001420modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001421provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001422URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001423"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001424
1425 # We do a little dance with the --gclientfile option. 'gclient config' is the
1426 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1427 # arguments. So, we temporarily stash any --gclientfile parameter into
1428 # options.output_config_file until after the (gclientfile xor spec) error
1429 # check.
1430 parser.remove_option('--gclientfile')
1431 parser.add_option('--gclientfile', dest='output_config_file',
1432 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001433 parser.add_option('--name',
1434 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001435 parser.add_option('--deps-file', default='DEPS',
1436 help='overrides the default name for the DEPS file for the'
1437 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001438 parser.add_option('--unmanaged', action='store_true', default=False,
1439 help='overrides the default behavior to make it possible '
1440 'to have the main solution untouched by gclient '
1441 '(gclient will check out unmanaged dependencies but '
1442 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001443 parser.add_option('--git-deps', action='store_true',
1444 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001445 parser.add_option('--cache-dir',
1446 help='(git only) Cache all git repos into this dir and do '
1447 'shared clones from the cache, instead of cloning '
1448 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001449 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001450 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001451 if options.output_config_file:
1452 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001453 if ((options.spec and args) or len(args) > 2 or
1454 (not options.spec and not args)):
1455 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1456
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001457 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001458 if options.spec:
1459 client.SetConfig(options.spec)
1460 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001461 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001462 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001463 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001464 if name.endswith('.git'):
1465 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001466 else:
1467 # specify an alternate relpath for the given URL.
1468 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001469 deps_file = options.deps_file
1470 if options.git_deps:
1471 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001472 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001473 if len(args) > 1:
1474 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001475 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001476 managed=not options.unmanaged,
1477 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001478 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001479 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001480
1481
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001482@attr('epilog', """Example:
1483 gclient pack > patch.txt
1484 generate simple patch for configured client and dependences
1485""")
1486def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001487 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001488
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001489Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001490dependencies, and performs minimal postprocessing of the output. The
1491resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001492checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001493"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001494 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1495 help='override deps for the specified (comma-separated) '
1496 'platform(s); \'all\' will process all deps_os '
1497 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001498 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001499 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001500 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001501 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001502 client = GClient.LoadCurrentConfig(options)
1503 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001504 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001505 if options.verbose:
1506 # Print out the .gclient file. This is longer than if we just printed the
1507 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001508 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001509 return client.RunOnDeps('pack', args)
1510
1511
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001512def CMDstatus(parser, args):
1513 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001514 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1515 help='override deps for the specified (comma-separated) '
1516 'platform(s); \'all\' will process all deps_os '
1517 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001518 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001519 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001520 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001521 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001522 if options.verbose:
1523 # Print out the .gclient file. This is longer than if we just printed the
1524 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001525 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001526 return client.RunOnDeps('status', args)
1527
1528
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001529@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001530 gclient sync
1531 update files from SCM according to current configuration,
1532 *for modules which have changed since last update or sync*
1533 gclient sync --force
1534 update files from SCM according to current configuration, for
1535 all modules (useful for recovering files deleted from local copy)
1536 gclient sync --revision src@31000
1537 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001538""")
1539def CMDsync(parser, args):
1540 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001541 parser.add_option('-f', '--force', action='store_true',
1542 help='force update even for unchanged modules')
1543 parser.add_option('-n', '--nohooks', action='store_true',
1544 help='don\'t run hooks after the update is complete')
1545 parser.add_option('-r', '--revision', action='append',
1546 dest='revisions', metavar='REV', default=[],
1547 help='Enforces revision/hash for the solutions with the '
1548 'format src@rev. The src@ part is optional and can be '
1549 'skipped. -r can be used multiple times when .gclient '
1550 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001551 'if the src@ part is skipped. Note that specifying '
1552 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001553 parser.add_option('--with_branch_heads', action='store_true',
1554 help='Clone git "branch_heads" refspecs in addition to '
1555 'the default refspecs. This adds about 1/2GB to a '
1556 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001557 parser.add_option('-t', '--transitive', action='store_true',
1558 help='When a revision is specified (in the DEPS file or '
1559 'with the command-line flag), transitively update '
1560 'the dependencies to the date of the given revision. '
1561 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001562 parser.add_option('-H', '--head', action='store_true',
1563 help='skips any safesync_urls specified in '
1564 'configured solutions and sync to head instead')
1565 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001566 help='Deletes from the working copy any dependencies that '
1567 'have been removed since the last sync, as long as '
1568 'there are no local modifications. When used with '
1569 '--force, such dependencies are removed even if they '
1570 'have local modifications. When used with --reset, '
1571 'all untracked directories are removed from the '
1572 'working copy, exclusing those which are explicitly '
1573 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001574 parser.add_option('-R', '--reset', action='store_true',
1575 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001576 parser.add_option('-M', '--merge', action='store_true',
1577 help='merge upstream changes instead of trying to '
1578 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001579 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1580 help='override deps for the specified (comma-separated) '
1581 'platform(s); \'all\' will process all deps_os '
1582 'references')
1583 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1584 help='Skip svn up whenever possible by requesting '
1585 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001586 parser.add_option('--upstream', action='store_true',
1587 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001588 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001589 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001590
1591 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001592 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001593
maruel@chromium.org307d1792010-05-31 20:03:13 +00001594 if options.revisions and options.head:
1595 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001596 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001597
1598 if options.verbose:
1599 # Print out the .gclient file. This is longer than if we just printed the
1600 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001601 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001602 return client.RunOnDeps('update', args)
1603
1604
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001605def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001606 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001607 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001608
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001609def CMDdiff(parser, args):
1610 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001611 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1612 help='override deps for the specified (comma-separated) '
1613 'platform(s); \'all\' will process all deps_os '
1614 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001615 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001616 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001617 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001618 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001619 if options.verbose:
1620 # Print out the .gclient file. This is longer than if we just printed the
1621 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001622 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001623 return client.RunOnDeps('diff', args)
1624
1625
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001626def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001627 """Revert all modifications in every dependencies.
1628
1629 That's the nuclear option to get back to a 'clean' state. It removes anything
1630 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001631 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1632 help='override deps for the specified (comma-separated) '
1633 'platform(s); \'all\' will process all deps_os '
1634 'references')
1635 parser.add_option('-n', '--nohooks', action='store_true',
1636 help='don\'t run hooks after the revert is complete')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001637 parser.add_option('--upstream', action='store_true',
1638 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001639 (options, args) = parser.parse_args(args)
1640 # --force is implied.
1641 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001642 options.reset = False
1643 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001644 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001645 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001646 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001647 return client.RunOnDeps('revert', args)
1648
1649
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001650def CMDrunhooks(parser, args):
1651 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001652 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1653 help='override deps for the specified (comma-separated) '
1654 'platform(s); \'all\' will process all deps_os '
1655 'references')
1656 parser.add_option('-f', '--force', action='store_true', default=True,
1657 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001658 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001659 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001660 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001661 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001662 if options.verbose:
1663 # Print out the .gclient file. This is longer than if we just printed the
1664 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001665 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001666 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001667 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001668 return client.RunOnDeps('runhooks', args)
1669
1670
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001671def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001672 """Output revision info mapping for the client and its dependencies.
1673
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001674 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001675 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001676 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1677 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001678 commit can change.
1679 """
1680 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1681 help='override deps for the specified (comma-separated) '
1682 'platform(s); \'all\' will process all deps_os '
1683 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001684 parser.add_option('-a', '--actual', action='store_true',
1685 help='gets the actual checked out revisions instead of the '
1686 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001687 parser.add_option('-s', '--snapshot', action='store_true',
1688 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001689 'version of all repositories to reproduce the tree, '
1690 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001691 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001692 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001693 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001694 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001695 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001696 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001697
1698
szager@google.comb9a78d32012-03-13 18:46:21 +00001699def CMDhookinfo(parser, args):
1700 """Output the hooks that would be run by `gclient runhooks`"""
1701 (options, args) = parser.parse_args(args)
1702 options.force = True
1703 client = GClient.LoadCurrentConfig(options)
1704 if not client:
1705 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1706 client.RunOnDeps(None, [])
1707 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1708 return 0
1709
1710
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001711def Command(name):
1712 return getattr(sys.modules[__name__], 'CMD' + name, None)
1713
1714
1715def CMDhelp(parser, args):
1716 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001717 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001718 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001719 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001720 parser.print_help()
1721 return 0
1722
1723
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001724def GenUsage(parser, command):
1725 """Modify an OptParse object with the function's documentation."""
1726 obj = Command(command)
1727 if command == 'help':
1728 command = '<command>'
1729 # OptParser.description prefer nicely non-formatted strings.
1730 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1731 usage = getattr(obj, 'usage', '')
1732 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1733 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001734
1735
maruel@chromium.org0895b752011-08-26 20:40:33 +00001736def Parser():
1737 """Returns the default parser."""
1738 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001739 # some arm boards have issues with parallel sync.
1740 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001741 jobs = 1
1742 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001743 jobs = max(8, gclient_utils.NumLocalCpus())
cmp@chromium.org3b37d342013-06-19 19:14:25 +00001744 # cmp: 2013/06/19
1745 # Temporary workaround to lower bot-load on SVN server.
1746 if os.environ.get('CHROME_HEADLESS') == '1':
ilevy@chromium.org413ffbc2013-06-20 02:35:38 +00001747 jobs = 4
szager@chromium.orge2e03202012-07-31 18:05:16 +00001748 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001749 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001750 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001751 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001752 parser.add_option('-v', '--verbose', action='count', default=0,
1753 help='Produces additional output for diagnostics. Can be '
1754 'used up to three times for more logging info.')
1755 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001756 default=None,
1757 help='Specify an alternate %s file' % gclientfile_default)
1758 parser.add_option('--spec',
1759 default=None,
1760 help='create a gclient file containing the provided '
1761 'string. Due to Cygwin/Python brokenness, it '
1762 'probably can\'t contain any newlines.')
szager@chromium.org41da24b2013-05-23 19:37:04 +00001763 parser.add_option('--no-nag-max', default=False, action='store_true',
1764 help='If a subprocess runs for too long without generating'
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001765 ' terminal output, generate warnings, but do not kill'
1766 ' the process.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001767 # Integrate standard options processing.
1768 old_parser = parser.parse_args
1769 def Parse(args):
1770 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001771 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1772 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001773 logging.basicConfig(level=level,
1774 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001775 if options.config_filename and options.spec:
1776 parser.error('Cannot specifiy both --gclientfile and --spec')
1777 if not options.config_filename:
1778 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001779 options.entries_filename = options.config_filename + '_entries'
1780 if options.jobs < 1:
1781 parser.error('--jobs must be 1 or higher')
1782
1783 # These hacks need to die.
1784 if not hasattr(options, 'revisions'):
1785 # GClient.RunOnDeps expects it even if not applicable.
1786 options.revisions = []
1787 if not hasattr(options, 'head'):
1788 options.head = None
1789 if not hasattr(options, 'nohooks'):
1790 options.nohooks = True
1791 if not hasattr(options, 'deps_os'):
1792 options.deps_os = None
1793 if not hasattr(options, 'manually_grab_svn_rev'):
1794 options.manually_grab_svn_rev = None
1795 if not hasattr(options, 'force'):
1796 options.force = None
szager@chromium.org41da24b2013-05-23 19:37:04 +00001797 if options.no_nag_max:
1798 gclient_scm.SCMWrapper.nag_max = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00001799 return (options, args)
1800 parser.parse_args = Parse
1801 # We don't want wordwrapping in epilog (usually examples)
1802 parser.format_epilog = lambda _: parser.epilog or ''
1803 return parser
1804
1805
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001806def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001807 """Doesn't parse the arguments here, just find the right subcommand to
1808 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001809 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001810 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001811 '\nYour python version %s is unsupported, please upgrade.\n' %
1812 sys.version.split(' ', 1)[0])
1813 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001814 if not sys.executable:
1815 print >> sys.stderr, (
1816 '\nPython cannot find the location of it\'s own executable.\n')
1817 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001818 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001819 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001820 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1821 # operations. Python as a strong tendency to buffer sys.stdout.
1822 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001823 # Make stdout annotated with the thread ids.
1824 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001825 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001826 # Unused variable 'usage'
1827 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001828 def to_str(fn):
1829 return (
1830 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1831 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1832 cmds = (
1833 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1834 )
1835 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001836 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001837 if argv:
1838 command = Command(argv[0])
1839 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001840 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001841 GenUsage(parser, argv[0])
1842 return command(parser, argv[1:])
1843 # Not a known command. Default to help.
1844 GenUsage(parser, 'help')
1845 return CMDhelp(parser, argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001846 except KeyboardInterrupt:
1847 gclient_utils.GClientChildren.KillAllRemainingChildren()
1848 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001849 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001850 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001851 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001852
1853
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001854if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001855 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001856 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001857
1858# vim: ts=2:sw=2:tw=80:et: