blob: d61540233362b4581ba8b5f7367f2560b8c57826 [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
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000085import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000086
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000087import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000088
maruel@chromium.org35625c72011-03-23 17:34:02 +000089import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000090import gclient_scm
91import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000092from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000093import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +000094from third_party import colorama
95# Import shortcut.
96from third_party.colorama import Fore
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000098
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000099def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000100 """Sets an attribute on a function."""
101 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +0000102 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000103 return fn
104 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000105
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000106
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000107## GClient implementation.
108
109
maruel@chromium.org116704f2010-06-11 17:34:38 +0000110class GClientKeywords(object):
111 class FromImpl(object):
112 """Used to implement the From() syntax."""
113
114 def __init__(self, module_name, sub_target_name=None):
115 """module_name is the dep module we want to include from. It can also be
116 the name of a subdirectory to include from.
117
118 sub_target_name is an optional parameter if the module name in the other
119 DEPS file is different. E.g., you might want to map src/net to net."""
120 self.module_name = module_name
121 self.sub_target_name = sub_target_name
122
123 def __str__(self):
124 return 'From(%s, %s)' % (repr(self.module_name),
125 repr(self.sub_target_name))
126
maruel@chromium.org116704f2010-06-11 17:34:38 +0000127 class FileImpl(object):
128 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000129 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000130
131 def __init__(self, file_location):
132 self.file_location = file_location
133
134 def __str__(self):
135 return 'File("%s")' % self.file_location
136
137 def GetPath(self):
138 return os.path.split(self.file_location)[0]
139
140 def GetFilename(self):
141 rev_tokens = self.file_location.split('@')
142 return os.path.split(rev_tokens[0])[1]
143
144 def GetRevision(self):
145 rev_tokens = self.file_location.split('@')
146 if len(rev_tokens) > 1:
147 return rev_tokens[1]
148 return None
149
150 class VarImpl(object):
151 def __init__(self, custom_vars, local_scope):
152 self._custom_vars = custom_vars
153 self._local_scope = local_scope
154
155 def Lookup(self, var_name):
156 """Implements the Var syntax."""
157 if var_name in self._custom_vars:
158 return self._custom_vars[var_name]
159 elif var_name in self._local_scope.get("vars", {}):
160 return self._local_scope["vars"][var_name]
161 raise gclient_utils.Error("Var is not defined: %s" % var_name)
162
163
maruel@chromium.org064186c2011-09-27 23:53:33 +0000164class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000165 """Immutable configuration settings."""
166 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000167 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000168 custom_hooks, deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000169 GClientKeywords.__init__(self)
170
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000171 # These are not mutable:
172 self._parent = parent
173 self._safesync_url = safesync_url
174 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000175 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000176 # 'managed' determines whether or not this dependency is synced/updated by
177 # gclient after gclient checks it out initially. The difference between
178 # 'managed' and 'should_process' is that the user specifies 'managed' via
179 # the --unmanaged command-line flag or a .gclient config, where
180 # 'should_process' is dynamically set by gclient if it goes over its
181 # recursion limit and controls gclient's behavior so it does not misbehave.
182 self._managed = managed
183 self._should_process = should_process
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000184 # This is a mutable value that overrides the normal recursion limit for this
185 # dependency. It is read from the actual DEPS file so cannot be set on
186 # class instantiation.
187 self.recursion_override = None
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000188 # This is a mutable value which has the list of 'target_os' OSes listed in
189 # the current deps file.
190 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000191
192 # These are only set in .gclient and not in DEPS files.
193 self._custom_vars = custom_vars or {}
194 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000195 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000196
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000197 # TODO(iannucci): Remove this when all masters are correctly substituting
198 # the new blink url.
199 if (self._custom_vars.get('webkit_trunk', '') ==
200 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000201 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
202 print 'Overwriting Var("webkit_trunk") with %s' % new_url
203 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000204
maruel@chromium.org064186c2011-09-27 23:53:33 +0000205 # Post process the url to remove trailing slashes.
206 if isinstance(self._url, basestring):
207 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
208 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000209 self._url = self._url.replace('/@', '@')
210 elif not isinstance(self._url,
211 (self.FromImpl, self.FileImpl, None.__class__)):
212 raise gclient_utils.Error(
213 ('dependency url must be either a string, None, '
214 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000215 # Make any deps_file path platform-appropriate.
216 for sep in ['/', '\\']:
217 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218
219 @property
220 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000221 return self._deps_file
222
223 @property
224 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 return self._managed
226
227 @property
228 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000229 return self._parent
230
231 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000232 def root(self):
233 """Returns the root node, a GClient object."""
234 if not self.parent:
235 # This line is to signal pylint that it could be a GClient instance.
236 return self or GClient(None, None)
237 return self.parent.root
238
239 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000241 return self._safesync_url
242
243 @property
244 def should_process(self):
245 """True if this dependency should be processed, i.e. checked out."""
246 return self._should_process
247
248 @property
249 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000250 return self._custom_vars.copy()
251
252 @property
253 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000254 return self._custom_deps.copy()
255
maruel@chromium.org064186c2011-09-27 23:53:33 +0000256 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000257 def custom_hooks(self):
258 return self._custom_hooks[:]
259
260 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000261 def url(self):
262 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000263
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000264 @property
265 def recursion_limit(self):
266 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000267 if self.recursion_override is not None:
268 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000269 return max(self.parent.recursion_limit - 1, 0)
270
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000271 @property
272 def target_os(self):
273 if self.local_target_os is not None:
274 return tuple(set(self.local_target_os).union(self.parent.target_os))
275 else:
276 return self.parent.target_os
277
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000278 def get_custom_deps(self, name, url):
279 """Returns a custom deps if applicable."""
280 if self.parent:
281 url = self.parent.get_custom_deps(name, url)
282 # None is a valid return value to disable a dependency.
283 return self.custom_deps.get(name, url)
284
maruel@chromium.org064186c2011-09-27 23:53:33 +0000285
286class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000287 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000288
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000289 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000290 custom_vars, custom_hooks, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000291 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000292 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000293 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000294 custom_hooks, deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000295
296 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000297 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000298
299 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000300 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000301 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000302 # A cache of the files affected by the current operation, necessary for
303 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000304 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000305 # If it is not set to True, the dependency wasn't processed for its child
306 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000307 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000308 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000309 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000310 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000311 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000312 # This is the scm used to checkout self.url. It may be used by dependencies
313 # to get the datetime of the revision we checked out.
314 self._used_scm = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000315
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000316 if not self.name and self.parent:
317 raise gclient_utils.Error('Dependency without name')
318
maruel@chromium.org470b5432011-10-11 18:18:19 +0000319 @property
320 def requirements(self):
321 """Calculate the list of requirements."""
322 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000323 # self.parent is implicitly a requirement. This will be recursive by
324 # definition.
325 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000326 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000327
328 # For a tree with at least 2 levels*, the leaf node needs to depend
329 # on the level higher up in an orderly way.
330 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
331 # thus unsorted, while the .gclient format is a list thus sorted.
332 #
333 # * _recursion_limit is hard coded 2 and there is no hope to change this
334 # value.
335 #
336 # Interestingly enough, the following condition only works in the case we
337 # want: self is a 2nd level node. 3nd level node wouldn't need this since
338 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000339 if self.parent and self.parent.parent and not self.parent.parent.parent:
340 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000341
342 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000343 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000344
maruel@chromium.org470b5432011-10-11 18:18:19 +0000345 if self.name:
346 requirements |= set(
347 obj.name for obj in self.root.subtree(False)
348 if (obj is not self
349 and obj.name and
350 self.name.startswith(posixpath.join(obj.name, ''))))
351 requirements = tuple(sorted(requirements))
352 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
353 return requirements
354
355 def verify_validity(self):
356 """Verifies that this Dependency is fine to add as a child of another one.
357
358 Returns True if this entry should be added, False if it is a duplicate of
359 another entry.
360 """
361 logging.info('Dependency(%s).verify_validity()' % self.name)
362 if self.name in [s.name for s in self.parent.dependencies]:
363 raise gclient_utils.Error(
364 'The same name "%s" appears multiple times in the deps section' %
365 self.name)
366 if not self.should_process:
367 # Return early, no need to set requirements.
368 return True
369
370 # This require a full tree traversal with locks.
371 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
372 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000373 self_url = self.LateOverride(self.url)
374 sibling_url = sibling.LateOverride(sibling.url)
375 # Allow to have only one to be None or ''.
376 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000377 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000378 ('Dependency %s specified more than once:\n'
379 ' %s [%s]\n'
380 'vs\n'
381 ' %s [%s]') % (
382 self.name,
383 sibling.hierarchy(),
384 sibling_url,
385 self.hierarchy(),
386 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000387 # In theory we could keep it as a shadow of the other one. In
388 # practice, simply ignore it.
389 logging.warn('Won\'t process duplicate dependency %s' % sibling)
390 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000391 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000392
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000393 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000394 """Resolves the parsed url from url.
395
396 Manages From() keyword accordingly. Do not touch self.parsed_url nor
397 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000398 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000399 parsed_url = self.get_custom_deps(self.name, url)
400 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000401 logging.info(
402 'Dependency(%s).LateOverride(%s) -> %s' %
403 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000404 return parsed_url
405
406 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000407 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000408 ref = [
409 dep for dep in self.root.subtree(True) if url.module_name == dep.name
410 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000411 if not ref:
412 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
413 url.module_name, ref))
414 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000415 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000416 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000417 found_deps = [d for d in ref.dependencies if d.name == sub_target]
418 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000419 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000420 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
421 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000422 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000423
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000424 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000425 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000426 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000427 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000428 'Dependency(%s).LateOverride(%s) -> %s (From)' %
429 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000430 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000431
432 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000433 parsed_url = urlparse.urlparse(url)
434 if not parsed_url[0]:
435 # A relative url. Fetch the real base.
436 path = parsed_url[2]
437 if not path.startswith('/'):
438 raise gclient_utils.Error(
439 'relative DEPS entry \'%s\' must begin with a slash' % url)
440 # Create a scm just to query the full url.
441 parent_url = self.parent.parsed_url
442 if isinstance(parent_url, self.FileImpl):
443 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000444 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000445 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000446 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000447 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000448 logging.info(
449 'Dependency(%s).LateOverride(%s) -> %s' %
450 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000451 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000452
453 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000454 logging.info(
455 'Dependency(%s).LateOverride(%s) -> %s (File)' %
456 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000457 return url
458
459 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000460 logging.info(
461 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000462 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000463
464 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000465
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000466 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000467 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000468 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000469 assert not self.dependencies
470 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000471 local_scope = {}
472 var = self.VarImpl(self.custom_vars, local_scope)
473 global_scope = {
474 'File': self.FileImpl,
475 'From': self.FromImpl,
476 'Var': var.Lookup,
477 'deps_os': {},
478 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000479 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000480 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000481 logging.info(
482 'ParseDepsFile(%s): No %s file found at %s' % (
483 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000484 else:
485 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000486 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000487 # Eval the content.
488 try:
489 exec(deps_content, global_scope, local_scope)
490 except SyntaxError, e:
491 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000492 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000493 if 'recursion' in local_scope:
494 self.recursion_override = local_scope.get('recursion')
495 logging.warning(
496 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000497 # If present, save 'target_os' in the local_target_os property.
498 if 'target_os' in local_scope:
499 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000500 # load os specific dependencies if defined. these dependencies may
501 # override or extend the values defined by the 'deps' member.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000502 target_os_deps = {}
maruel@chromium.org271375b2010-06-23 19:17:38 +0000503 if 'deps_os' in local_scope:
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000504 for deps_os_key in self.target_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000505 os_deps = local_scope['deps_os'].get(deps_os_key, {})
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000506 if len(self.target_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000507 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000508 # platform, so we collect the broadest set of dependencies
509 # available. We may end up with the wrong revision of something for
510 # our platform, but this is the best we can do.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000511 target_os_deps.update(
512 [x for x in os_deps.items() if not x[0] in target_os_deps])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000513 else:
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000514 target_os_deps.update(os_deps)
515
516 # deps_os overrides paths from deps
517 deps.update(target_os_deps)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000518
maruel@chromium.org271375b2010-06-23 19:17:38 +0000519 # If a line is in custom_deps, but not in the solution, we want to append
520 # this line to the solution.
521 for d in self.custom_deps:
522 if d not in deps:
523 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000524
525 # If use_relative_paths is set in the DEPS file, regenerate
526 # the dictionary using paths relative to the directory containing
527 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000528 use_relative_paths = local_scope.get('use_relative_paths', False)
529 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000530 rel_deps = {}
531 for d, url in deps.items():
532 # normpath is required to allow DEPS to use .. in their
533 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000534 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
535 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000536
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000537 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000538 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000539 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000540 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000541 deps_to_add.append(Dependency(
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000542 self, name, url, None, None, None, None, None,
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000543 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000544 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000545
546 # override named sets of hooks by the custom hooks
547 hooks_to_run = []
548 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
549 for hook in local_scope.get('hooks', []):
550 if hook.get('name', '') not in hook_names_to_suppress:
551 hooks_to_run.append(hook)
552
553 # add the replacements and any additions
554 for hook in self.custom_hooks:
555 if 'action' in hook:
556 hooks_to_run.append(hook)
557
558 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000559 logging.info('ParseDepsFile(%s) done' % self.name)
560
561 def add_dependencies_and_close(self, deps_to_add, hooks):
562 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000563 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000564 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000565 self.add_dependency(dep)
566 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000567
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000568 def maybeGetParentRevision(
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000569 self, command, options, parsed_url, parent_name, revision_overrides):
570 """Uses revision/timestamp of parent if no explicit revision was specified.
571
572 If we are performing an update and --transitive is set, use
573 - the parent's revision if 'self.url' is in the same repository
574 - the parent's timestamp otherwise
575 to update 'self.url'. The used revision/timestamp will be set in
576 'options.revision'.
577 If we have an explicit revision do nothing.
578 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000579 if command == 'update' and options.transitive and not options.revision:
580 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
581 if not revision:
582 options.revision = revision_overrides.get(parent_name)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000583 if (options.revision and
584 not gclient_utils.IsDateRevision(options.revision)):
585 assert self.parent and self.parent.used_scm
586 # If this dependency is in the same repository as parent it's url will
587 # start with a slash. If so we take the parent revision instead of
588 # it's timestamp.
589 # (The timestamps of commits in google code are broken -- which can
590 # result in dependencies to be checked out at the wrong revision)
591 if self.url.startswith('/'):
592 if options.verbose:
593 print('Using parent\'s revision %s since we are in the same '
594 'repository.' % options.revision)
595 else:
596 parent_revision_date = self.parent.used_scm.GetRevisionDate(
597 options.revision)
598 options.revision = gclient_utils.MakeDateRevision(
599 parent_revision_date)
600 if options.verbose:
601 print('Using parent\'s revision date %s since we are in a '
602 'different repository.' % options.revision)
603 revision_overrides[self.name] = options.revision
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000604
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000605 # Arguments number differs from overridden method
606 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000607 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000608 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000609 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000610 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000611 if not self.should_process:
612 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000613 # When running runhooks, there's no need to consult the SCM.
614 # All known hooks are expected to run unconditionally regardless of working
615 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000616 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000617 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000618 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000619 if run_scm and parsed_url:
620 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000621 # Special support for single-file checkout.
622 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000623 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
624 # pylint: disable=E1103
625 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000626 self._used_scm = gclient_scm.SVNWrapper(
627 parsed_url.GetPath(), self.root.root_dir, self.name)
628 self._used_scm.RunCommand('updatesingle',
629 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000630 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000631 # Create a shallow copy to mutate revision.
632 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000633 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000634 self.maybeGetParentRevision(
635 command, options, parsed_url, self.parent.name, revision_overrides)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000636 self._used_scm = gclient_scm.CreateSCM(
637 parsed_url, self.root.root_dir, self.name)
638 self._used_scm.RunCommand(command, options, args, file_list)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000639 if file_list:
640 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000641
642 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
643 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000644 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000645 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000646 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000647 continue
648 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000649 [self.root.root_dir.lower(), file_list[i].lower()])
650 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000651 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000652 while file_list[i].startswith(('\\', '/')):
653 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000654
655 # Always parse the DEPS file.
656 self.ParseDepsFile()
657
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000658 self._run_is_done(file_list or [], parsed_url)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000659
660 if self.recursion_limit:
661 # Parse the dependencies of this dependency.
662 for s in self.dependencies:
663 work_queue.enqueue(s)
664
665 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000666 if not isinstance(parsed_url, self.FileImpl):
667 # Skip file only checkout.
668 scm = gclient_scm.GetScmName(parsed_url)
669 if not options.scm or scm in options.scm:
670 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
671 # Pass in the SCM type as an env variable
672 env = os.environ.copy()
673 if scm:
674 env['GCLIENT_SCM'] = scm
675 if parsed_url:
676 env['GCLIENT_URL'] = parsed_url
ilevy@chromium.org37116242012-11-28 01:32:48 +0000677 env['GCLIENT_DEP_PATH'] = self.name
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000678 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000679 print_stdout = False
680 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000681 """Git-specific path marshaling. It is optimized for git-grep."""
682
683 def mod_path(git_pathspec):
684 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
685 modified_path = os.path.join(self.name, match.group(2))
686 branch = match.group(1) or ''
687 return '%s%s' % (branch, modified_path)
688
689 match = re.match('^Binary file ([^\0]+) matches$', line)
690 if match:
691 print 'Binary file %s matches' % mod_path(match.group(1))
692 return
693
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000694 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000695 if len(items) == 2 and items[1]:
696 print '%s : %s' % (mod_path(items[0]), items[1])
697 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000698 # Multiple null bytes or a single trailing null byte indicate
699 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000700 print '\n'.join(mod_path(path) for path in items if path)
701 else:
702 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000703 else:
704 print_stdout = True
705 filter_fn = None
706
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000707 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000708 try:
709 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000710 args, cwd=cwd, env=env, print_stdout=print_stdout,
711 filter_fn=filter_fn,
712 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000713 except subprocess2.CalledProcessError:
714 if not options.ignore:
715 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000716 else:
717 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000718
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000719
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000720 @gclient_utils.lockedmethod
721 def _run_is_done(self, file_list, parsed_url):
722 # Both these are kept for hooks that are run as a separate tree traversal.
723 self._file_list = file_list
724 self._parsed_url = parsed_url
725 self._processed = True
726
szager@google.comb9a78d32012-03-13 18:46:21 +0000727 @staticmethod
728 def GetHookAction(hook_dict, matching_file_list):
729 """Turns a parsed 'hook' dict into an executable command."""
730 logging.debug(hook_dict)
731 logging.debug(matching_file_list)
732 command = hook_dict['action'][:]
733 if command[0] == 'python':
734 # If the hook specified "python" as the first item, the action is a
735 # Python script. Run it by starting a new copy of the same
736 # interpreter.
737 command[0] = sys.executable
738 if '$matching_files' in command:
739 splice_index = command.index('$matching_files')
740 command[splice_index:splice_index + 1] = matching_file_list
741 return command
742
743 def GetHooks(self, options):
744 """Evaluates all hooks, and return them in a flat list.
745
746 RunOnDeps() must have been called before to load the DEPS.
747 """
748 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000749 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000750 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000751 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000752 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000753 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000754 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000755 # TODO(maruel): If the user is using git or git-svn, then we don't know
756 # what files have changed so we always run all hooks. It'd be nice to fix
757 # that.
758 if (options.force or
759 isinstance(self.parsed_url, self.FileImpl) or
760 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000761 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000762 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000763 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000764 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000765 # Run hooks on the basis of whether the files from the gclient operation
766 # match each hook's pattern.
767 for hook_dict in self.deps_hooks:
768 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000769 matching_file_list = [
770 f for f in self.file_list_and_children if pattern.search(f)
771 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000772 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000773 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000774 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000775 result.extend(s.GetHooks(options))
776 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000777
szager@google.comb9a78d32012-03-13 18:46:21 +0000778 def RunHooksRecursively(self, options):
779 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000780 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000781 for hook in self.GetHooks(options):
782 try:
783 gclient_utils.CheckCallAndFilterAndHeader(
784 hook, cwd=self.root.root_dir, always=True)
785 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
786 # Use a discrete exit status code of 2 to indicate that a hook action
787 # failed. Users of this script may wish to treat hook action failures
788 # differently from VC failures.
789 print >> sys.stderr, 'Error: %s' % str(e)
790 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000791
maruel@chromium.org0d812442010-08-10 12:41:08 +0000792 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000793 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000794 dependencies = self.dependencies
795 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000796 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000797 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000798 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000799 for i in d.subtree(include_all):
800 yield i
801
802 def depth_first_tree(self):
803 """Depth-first recursion including the root node."""
804 yield self
805 for i in self.dependencies:
806 for j in i.depth_first_tree():
807 if j.should_process:
808 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000809
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000810 @gclient_utils.lockedmethod
811 def add_dependency(self, new_dep):
812 self._dependencies.append(new_dep)
813
814 @gclient_utils.lockedmethod
815 def _mark_as_parsed(self, new_hooks):
816 self._deps_hooks.extend(new_hooks)
817 self._deps_parsed = True
818
maruel@chromium.org68988972011-09-20 14:11:42 +0000819 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000820 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000821 def dependencies(self):
822 return tuple(self._dependencies)
823
824 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000825 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000826 def deps_hooks(self):
827 return tuple(self._deps_hooks)
828
829 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000830 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000831 def parsed_url(self):
832 return self._parsed_url
833
834 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000835 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000836 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000837 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000838 return self._deps_parsed
839
840 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000841 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000842 def processed(self):
843 return self._processed
844
845 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000846 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000847 def hooks_ran(self):
848 return self._hooks_ran
849
850 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000851 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000852 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000853 return tuple(self._file_list)
854
855 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000856 def used_scm(self):
857 """SCMWrapper instance for this dependency or None if not processed yet."""
858 return self._used_scm
859
860 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000861 def file_list_and_children(self):
862 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000863 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000864 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000865 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000866
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000867 def __str__(self):
868 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000869 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000870 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000871 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000872 # First try the native property if it exists.
873 if hasattr(self, '_' + i):
874 value = getattr(self, '_' + i, False)
875 else:
876 value = getattr(self, i, False)
877 if value:
878 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000879
880 for d in self.dependencies:
881 out.extend([' ' + x for x in str(d).splitlines()])
882 out.append('')
883 return '\n'.join(out)
884
885 def __repr__(self):
886 return '%s: %s' % (self.name, self.url)
887
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000888 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000889 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000890 out = '%s(%s)' % (self.name, self.url)
891 i = self.parent
892 while i and i.name:
893 out = '%s(%s) -> %s' % (i.name, i.url, out)
894 i = i.parent
895 return out
896
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000897
898class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000899 """Object that represent a gclient checkout. A tree of Dependency(), one per
900 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000901
902 DEPS_OS_CHOICES = {
903 "win32": "win",
904 "win": "win",
905 "cygwin": "win",
906 "darwin": "mac",
907 "mac": "mac",
908 "unix": "unix",
909 "linux": "unix",
910 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000911 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000912 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000913 }
914
915 DEFAULT_CLIENT_FILE_TEXT = ("""\
916solutions = [
917 { "name" : "%(solution_name)s",
918 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000919 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000920 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000921 "custom_deps" : {
922 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000923 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000924 },
925]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000926cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000927""")
928
929 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
930 { "name" : "%(solution_name)s",
931 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000932 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000933 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000934 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000935%(solution_deps)s },
936 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000937 },
938""")
939
940 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
941# Snapshot generated with gclient revinfo --snapshot
942solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000943%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000944""")
945
946 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000947 # Do not change previous behavior. Only solution level and immediate DEPS
948 # are processed.
949 self._recursion_limit = 2
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000950 Dependency.__init__(self, None, None, None, None, True, None, None, None,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000951 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000952 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000953 if options.deps_os:
954 enforced_os = options.deps_os.split(',')
955 else:
956 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
957 if 'all' in enforced_os:
958 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000959 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000960 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000961 self.config_content = None
962
963 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000964 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000965 config_dict = {}
966 self.config_content = content
967 try:
968 exec(content, config_dict)
969 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000970 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000971
peter@chromium.org1efccc82012-04-27 16:34:38 +0000972 # Append any target OS that is not already being enforced to the tuple.
973 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000974 if config_dict.get('target_os_only', False):
975 self._enforced_os = tuple(set(target_os))
976 else:
977 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
978
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000979 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
980
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000981 if not target_os and config_dict.get('target_os_only', False):
982 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
983 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +0000984
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000985 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000986 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000987 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000988 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000989 self, s['name'], s['url'],
990 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000991 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000992 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000993 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000994 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000995 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000996 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000997 except KeyError:
998 raise gclient_utils.Error('Invalid .gclient file. Solution is '
999 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001000 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1001 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001002
1003 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001004 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001005 self._options.config_filename),
1006 self.config_content)
1007
1008 @staticmethod
1009 def LoadCurrentConfig(options):
1010 """Searches for and loads a .gclient file relative to the current working
1011 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001012 if options.spec:
1013 client = GClient('.', options)
1014 client.SetConfig(options.spec)
1015 else:
1016 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1017 if not path:
1018 return None
1019 client = GClient(path, options)
1020 client.SetConfig(gclient_utils.FileRead(
1021 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001022
1023 if (options.revisions and
1024 len(client.dependencies) > 1 and
1025 any('@' not in r for r in options.revisions)):
1026 print >> sys.stderr, (
1027 'You must specify the full solution name like --revision %s@%s\n'
1028 'when you have multiple solutions setup in your .gclient file.\n'
1029 'Other solutions present are: %s.') % (
1030 client.dependencies[0].name,
1031 options.revisions[0],
1032 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001033 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001034
nsylvain@google.comefc80932011-05-31 21:27:56 +00001035 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001036 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001037 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1038 'solution_name': solution_name,
1039 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001040 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001041 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001042 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001043 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001044 })
1045
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001046 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001047 """Creates a .gclient_entries file to record the list of unique checkouts.
1048
1049 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001050 """
1051 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1052 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001053 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001054 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001055 # Skip over File() dependencies as we can't version them.
1056 if not isinstance(entry.parsed_url, self.FileImpl):
1057 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1058 pprint.pformat(entry.parsed_url))
1059 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001060 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001061 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001062 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001063
1064 def _ReadEntries(self):
1065 """Read the .gclient_entries file for the given client.
1066
1067 Returns:
1068 A sequence of solution names, which will be empty if there is the
1069 entries file hasn't been created yet.
1070 """
1071 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001072 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001073 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001074 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001075 try:
1076 exec(gclient_utils.FileRead(filename), scope)
1077 except SyntaxError, e:
1078 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001079 return scope['entries']
1080
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001081 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001082 """Checks for revision overrides."""
1083 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001084 if self._options.head:
1085 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001086 # Do not check safesync_url if one or more --revision flag is specified.
1087 if not self._options.revisions:
1088 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001089 if not s.managed:
1090 self._options.revisions.append('%s@unmanaged' % s.name)
1091 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001092 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001093 if not self._options.revisions:
1094 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001095 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001096 index = 0
1097 for revision in self._options.revisions:
1098 if not '@' in revision:
1099 # Support for --revision 123
1100 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001101 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001102 if not sol in solutions_names:
1103 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1104 print >> sys.stderr, ('Please fix your script, having invalid '
1105 '--revision flags will soon considered an error.')
1106 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001107 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001108 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001109 return revision_overrides
1110
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001111 def _ApplySafeSyncRev(self, dep):
1112 """Finds a valid revision from the content of the safesync_url and apply it
1113 by appending revisions to the revision list. Throws if revision appears to
1114 be invalid for the given |dep|."""
1115 assert len(dep.safesync_url) > 0
1116 handle = urllib.urlopen(dep.safesync_url)
1117 rev = handle.read().strip()
1118 handle.close()
1119 if not rev:
1120 raise gclient_utils.Error(
1121 'It appears your safesync_url (%s) is not working properly\n'
1122 '(as it returned an empty response). Check your config.' %
1123 dep.safesync_url)
1124 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
iannucci@chromium.org4a4b33b2013-07-04 20:25:46 +00001125 safe_rev = scm.GetUsableRev(rev, self._options)
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001126 if self._options.verbose:
1127 print('Using safesync_url revision: %s.\n' % safe_rev)
1128 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1129
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001130 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131 """Runs a command on each dependency in a client and its dependencies.
1132
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001133 Args:
1134 command: The command to use (e.g., 'status' or 'diff')
1135 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001136 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001137 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001138 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001139 revision_overrides = {}
1140 # It's unnecessary to check for revision overrides for 'recurse'.
1141 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001142 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001143 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001144 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001145 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001146 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001147 if command in ('update', 'revert'):
1148 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001149 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001150 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001151 work_queue = gclient_utils.ExecutionQueue(
1152 self._options.jobs, pm, ignore_requirements=ignore_requirements)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001153 for s in self.dependencies:
1154 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001155 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001156
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001157 # Once all the dependencies have been processed, it's now safe to run the
1158 # hooks.
1159 if not self._options.nohooks:
1160 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001161
1162 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001163 # Notify the user if there is an orphaned entry in their working copy.
1164 # Only delete the directory if there are no changes in it, and
1165 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001166 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001167 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1168 for e in entries]
1169
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001170 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001171 if not prev_url:
1172 # entry must have been overridden via .gclient custom_deps
1173 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001174 # Fix path separator on Windows.
1175 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001176 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001177
1178 def _IsParentOfAny(parent, path_list):
1179 parent_plus_slash = parent + '/'
1180 return any(
1181 path[:len(parent_plus_slash)] == parent_plus_slash
1182 for path in path_list)
1183
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001184 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001185 if (entry not in entries and
1186 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001187 os.path.exists(e_dir)):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001188 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001189
1190 # Check to see if this directory is now part of a higher-up checkout.
1191 if scm.GetCheckoutRoot() in full_entries:
1192 logging.info('%s is part of a higher level checkout, not '
1193 'removing.', scm.GetCheckoutRoot())
1194 continue
1195
1196 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001197 scm.status(self._options, [], file_list)
1198 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001199 if (not self._options.delete_unversioned_trees or
1200 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001201 # There are modified files in this entry. Keep warning until
1202 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001203 print(('\nWARNING: \'%s\' is no longer part of this client. '
1204 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001205 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001206 else:
1207 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001208 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001209 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001210 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001211 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001212 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001213 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001214
1215 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001216 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001217 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001218 # Load all the settings.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001219 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None, False)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001220 for s in self.dependencies:
1221 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001222 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001223
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001224 def GetURLAndRev(dep):
1225 """Returns the revision-qualified SCM url for a Dependency."""
1226 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001227 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001228 if isinstance(dep.parsed_url, self.FileImpl):
1229 original_url = dep.parsed_url.file_location
1230 else:
1231 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001232 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001233 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001234 if not os.path.isdir(scm.checkout_path):
1235 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001236 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001237
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001238 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001239 new_gclient = ''
1240 # First level at .gclient
1241 for d in self.dependencies:
1242 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001243 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001244 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001245 for d in dep.dependencies:
1246 entries[d.name] = GetURLAndRev(d)
1247 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001248 GrabDeps(d)
1249 custom_deps = []
1250 for k in sorted(entries.keys()):
1251 if entries[k]:
1252 # Quotes aren't escaped...
1253 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1254 else:
1255 custom_deps.append(' \"%s\": None,\n' % k)
1256 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1257 'solution_name': d.name,
1258 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001259 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001260 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001261 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001262 'solution_deps': ''.join(custom_deps),
1263 }
1264 # Print the snapshot configuration file
1265 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001266 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001267 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001268 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001269 if self._options.actual:
1270 entries[d.name] = GetURLAndRev(d)
1271 else:
1272 entries[d.name] = d.parsed_url
1273 keys = sorted(entries.keys())
1274 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001275 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001276 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001277
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001278 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001279 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001280 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001281
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001282 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001283 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001284 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001285 return self._root_dir
1286
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001287 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001288 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001289 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001290 return self._enforced_os
1291
maruel@chromium.org68988972011-09-20 14:11:42 +00001292 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001293 def recursion_limit(self):
1294 """How recursive can each dependencies in DEPS file can load DEPS file."""
1295 return self._recursion_limit
1296
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001297 @property
1298 def target_os(self):
1299 return self._enforced_os
1300
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001301
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001302#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001303
1304
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001305def CMDcleanup(parser, args):
1306 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001307
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001308Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001309"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001310 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1311 help='override deps for the specified (comma-separated) '
1312 'platform(s); \'all\' will process all deps_os '
1313 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001314 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001315 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001316 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001317 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001318 if options.verbose:
1319 # Print out the .gclient file. This is longer than if we just printed the
1320 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001321 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001322 return client.RunOnDeps('cleanup', args)
1323
1324
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001325@attr('usage', '[command] [args ...]')
1326def CMDrecurse(parser, args):
1327 """Operates on all the entries.
1328
1329 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001330 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1331 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001332 """
1333 # Stop parsing at the first non-arg so that these go through to the command
1334 parser.disable_interspersed_args()
1335 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001336 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001337 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001338 help='Ignore non-zero return codes from subcommands.')
1339 parser.add_option('--prepend-dir', action='store_true',
1340 help='Prepend relative dir for use with git <cmd> --null.')
1341 parser.add_option('--no-progress', action='store_true',
1342 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001343 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001344 if not args:
1345 print >> sys.stderr, 'Need to supply a command!'
1346 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001347 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1348 if not root_and_entries:
1349 print >> sys.stderr, (
1350 'You need to run gclient sync at least once to use \'recurse\'.\n'
1351 'This is because .gclient_entries needs to exist and be up to date.')
1352 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001353
1354 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001355 scm_set = set()
1356 for scm in options.scm:
1357 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001358 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001359
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001360 options.nohooks = True
1361 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001362 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1363 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001364
1365
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001366@attr('usage', '[args ...]')
1367def CMDfetch(parser, args):
1368 """Fetches upstream commits for all modules.
1369
1370Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1371"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001372 (options, args) = parser.parse_args(args)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001373 return CMDrecurse(Parser(), [
1374 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1375
1376
1377def CMDgrep(parser, args):
1378 """Greps through git repos managed by gclient.
1379
1380Runs 'git grep [args...]' for each module.
1381"""
1382
1383 # We can't use optparse because it will try to parse arguments sent
1384 # to git grep and throw an error. :-(
1385 if not args or re.match('(-h|--help)$', args[0]):
1386 print >> sys.stderr, (
1387 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1388 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1389 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1390 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1391 ' end of your query.'
1392 )
1393 return 1
1394
1395 jobs_arg = ['--jobs=1']
1396 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1397 jobs_arg, args = args[:1], args[1:]
1398 elif re.match(r'(-j|--jobs)$', args[0]):
1399 jobs_arg, args = args[:2], args[2:]
1400
1401 return CMDrecurse(
1402 parser,
1403 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1404 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001405
1406
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001407@attr('usage', '[url] [safesync url]')
1408def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001409 """Create a .gclient file in the current directory.
1410
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001411This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001412top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001413modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001414provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001415URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001416"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001417
1418 # We do a little dance with the --gclientfile option. 'gclient config' is the
1419 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1420 # arguments. So, we temporarily stash any --gclientfile parameter into
1421 # options.output_config_file until after the (gclientfile xor spec) error
1422 # check.
1423 parser.remove_option('--gclientfile')
1424 parser.add_option('--gclientfile', dest='output_config_file',
1425 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001426 parser.add_option('--name',
1427 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001428 parser.add_option('--deps-file', default='DEPS',
1429 help='overrides the default name for the DEPS file for the'
1430 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001431 parser.add_option('--unmanaged', action='store_true', default=False,
1432 help='overrides the default behavior to make it possible '
1433 'to have the main solution untouched by gclient '
1434 '(gclient will check out unmanaged dependencies but '
1435 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001436 parser.add_option('--git-deps', action='store_true',
1437 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001438 parser.add_option('--cache-dir',
1439 help='(git only) Cache all git repos into this dir and do '
1440 'shared clones from the cache, instead of cloning '
1441 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001442 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001443 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001444 if options.output_config_file:
1445 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001446 if ((options.spec and args) or len(args) > 2 or
1447 (not options.spec and not args)):
1448 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1449
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001450 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001451 if options.spec:
1452 client.SetConfig(options.spec)
1453 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001454 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001455 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001456 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001457 if name.endswith('.git'):
1458 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001459 else:
1460 # specify an alternate relpath for the given URL.
1461 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001462 deps_file = options.deps_file
1463 if options.git_deps:
1464 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001465 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001466 if len(args) > 1:
1467 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001468 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001469 managed=not options.unmanaged,
1470 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001471 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001472 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001473
1474
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001475@attr('epilog', """Example:
1476 gclient pack > patch.txt
1477 generate simple patch for configured client and dependences
1478""")
1479def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001480 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001481
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001482Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001483dependencies, and performs minimal postprocessing of the output. The
1484resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001485checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001486"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001487 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1488 help='override deps for the specified (comma-separated) '
1489 'platform(s); \'all\' will process all deps_os '
1490 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001491 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001492 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001493 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001494 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001495 client = GClient.LoadCurrentConfig(options)
1496 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001497 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001498 if options.verbose:
1499 # Print out the .gclient file. This is longer than if we just printed the
1500 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001501 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001502 return client.RunOnDeps('pack', args)
1503
1504
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001505def CMDstatus(parser, args):
1506 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001507 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1508 help='override deps for the specified (comma-separated) '
1509 'platform(s); \'all\' will process all deps_os '
1510 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001511 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001512 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001513 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001514 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001515 if options.verbose:
1516 # Print out the .gclient file. This is longer than if we just printed the
1517 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001518 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001519 return client.RunOnDeps('status', args)
1520
1521
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001522@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001523 gclient sync
1524 update files from SCM according to current configuration,
1525 *for modules which have changed since last update or sync*
1526 gclient sync --force
1527 update files from SCM according to current configuration, for
1528 all modules (useful for recovering files deleted from local copy)
1529 gclient sync --revision src@31000
1530 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001531""")
1532def CMDsync(parser, args):
1533 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001534 parser.add_option('-f', '--force', action='store_true',
1535 help='force update even for unchanged modules')
1536 parser.add_option('-n', '--nohooks', action='store_true',
1537 help='don\'t run hooks after the update is complete')
1538 parser.add_option('-r', '--revision', action='append',
1539 dest='revisions', metavar='REV', default=[],
1540 help='Enforces revision/hash for the solutions with the '
1541 'format src@rev. The src@ part is optional and can be '
1542 'skipped. -r can be used multiple times when .gclient '
1543 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001544 'if the src@ part is skipped. Note that specifying '
1545 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001546 parser.add_option('--with_branch_heads', action='store_true',
1547 help='Clone git "branch_heads" refspecs in addition to '
1548 'the default refspecs. This adds about 1/2GB to a '
1549 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001550 parser.add_option('-t', '--transitive', action='store_true',
1551 help='When a revision is specified (in the DEPS file or '
1552 'with the command-line flag), transitively update '
1553 'the dependencies to the date of the given revision. '
1554 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001555 parser.add_option('-H', '--head', action='store_true',
1556 help='skips any safesync_urls specified in '
1557 'configured solutions and sync to head instead')
1558 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001559 help='Deletes from the working copy any dependencies that '
1560 'have been removed since the last sync, as long as '
1561 'there are no local modifications. When used with '
1562 '--force, such dependencies are removed even if they '
1563 'have local modifications. When used with --reset, '
1564 'all untracked directories are removed from the '
1565 'working copy, exclusing those which are explicitly '
1566 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001567 parser.add_option('-R', '--reset', action='store_true',
1568 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001569 parser.add_option('-M', '--merge', action='store_true',
1570 help='merge upstream changes instead of trying to '
1571 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001572 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1573 help='override deps for the specified (comma-separated) '
1574 'platform(s); \'all\' will process all deps_os '
1575 'references')
1576 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1577 help='Skip svn up whenever possible by requesting '
1578 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001579 parser.add_option('--upstream', action='store_true',
1580 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001581 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001582 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001583
1584 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001585 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001586
maruel@chromium.org307d1792010-05-31 20:03:13 +00001587 if options.revisions and options.head:
1588 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001589 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001590
1591 if options.verbose:
1592 # Print out the .gclient file. This is longer than if we just printed the
1593 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001594 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001595 return client.RunOnDeps('update', args)
1596
1597
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001598def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001599 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001600 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001601
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001602def CMDdiff(parser, args):
1603 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001604 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1605 help='override deps for the specified (comma-separated) '
1606 'platform(s); \'all\' will process all deps_os '
1607 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001608 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001609 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001610 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001611 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001612 if options.verbose:
1613 # Print out the .gclient file. This is longer than if we just printed the
1614 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001615 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001616 return client.RunOnDeps('diff', args)
1617
1618
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001619def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001620 """Revert all modifications in every dependencies.
1621
1622 That's the nuclear option to get back to a 'clean' state. It removes anything
1623 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001624 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1625 help='override deps for the specified (comma-separated) '
1626 'platform(s); \'all\' will process all deps_os '
1627 'references')
1628 parser.add_option('-n', '--nohooks', action='store_true',
1629 help='don\'t run hooks after the revert is complete')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001630 parser.add_option('--upstream', action='store_true',
1631 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001632 (options, args) = parser.parse_args(args)
1633 # --force is implied.
1634 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001635 options.reset = False
1636 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001637 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001638 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001639 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001640 return client.RunOnDeps('revert', args)
1641
1642
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001643def CMDrunhooks(parser, args):
1644 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001645 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1646 help='override deps for the specified (comma-separated) '
1647 'platform(s); \'all\' will process all deps_os '
1648 'references')
1649 parser.add_option('-f', '--force', action='store_true', default=True,
1650 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001651 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001652 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001653 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001654 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001655 if options.verbose:
1656 # Print out the .gclient file. This is longer than if we just printed the
1657 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001658 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001659 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001660 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001661 return client.RunOnDeps('runhooks', args)
1662
1663
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001664def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001665 """Output revision info mapping for the client and its dependencies.
1666
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001667 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001668 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001669 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1670 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001671 commit can change.
1672 """
1673 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1674 help='override deps for the specified (comma-separated) '
1675 'platform(s); \'all\' will process all deps_os '
1676 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001677 parser.add_option('-a', '--actual', action='store_true',
1678 help='gets the actual checked out revisions instead of the '
1679 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001680 parser.add_option('-s', '--snapshot', action='store_true',
1681 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001682 'version of all repositories to reproduce the tree, '
1683 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001684 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001685 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001686 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001687 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001688 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001689 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001690
1691
szager@google.comb9a78d32012-03-13 18:46:21 +00001692def CMDhookinfo(parser, args):
1693 """Output the hooks that would be run by `gclient runhooks`"""
1694 (options, args) = parser.parse_args(args)
1695 options.force = True
1696 client = GClient.LoadCurrentConfig(options)
1697 if not client:
1698 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1699 client.RunOnDeps(None, [])
1700 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1701 return 0
1702
1703
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001704def Command(name):
1705 return getattr(sys.modules[__name__], 'CMD' + name, None)
1706
1707
1708def CMDhelp(parser, args):
1709 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001710 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001711 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001712 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001713 parser.print_help()
1714 return 0
1715
1716
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001717def GenUsage(parser, command):
1718 """Modify an OptParse object with the function's documentation."""
1719 obj = Command(command)
1720 if command == 'help':
1721 command = '<command>'
1722 # OptParser.description prefer nicely non-formatted strings.
1723 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1724 usage = getattr(obj, 'usage', '')
1725 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1726 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001727
1728
maruel@chromium.org0895b752011-08-26 20:40:33 +00001729def Parser():
1730 """Returns the default parser."""
1731 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001732 # some arm boards have issues with parallel sync.
1733 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001734 jobs = 1
1735 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001736 jobs = max(8, gclient_utils.NumLocalCpus())
cmp@chromium.org3b37d342013-06-19 19:14:25 +00001737 # cmp: 2013/06/19
1738 # Temporary workaround to lower bot-load on SVN server.
1739 if os.environ.get('CHROME_HEADLESS') == '1':
ilevy@chromium.org413ffbc2013-06-20 02:35:38 +00001740 jobs = 4
szager@chromium.orge2e03202012-07-31 18:05:16 +00001741 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001742 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001743 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001744 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001745 parser.add_option('-v', '--verbose', action='count', default=0,
1746 help='Produces additional output for diagnostics. Can be '
1747 'used up to three times for more logging info.')
1748 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001749 default=None,
1750 help='Specify an alternate %s file' % gclientfile_default)
1751 parser.add_option('--spec',
1752 default=None,
1753 help='create a gclient file containing the provided '
1754 'string. Due to Cygwin/Python brokenness, it '
1755 'probably can\'t contain any newlines.')
szager@chromium.org41da24b2013-05-23 19:37:04 +00001756 parser.add_option('--no-nag-max', default=False, action='store_true',
1757 help='If a subprocess runs for too long without generating'
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001758 ' terminal output, generate warnings, but do not kill'
1759 ' the process.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001760 # Integrate standard options processing.
1761 old_parser = parser.parse_args
1762 def Parse(args):
1763 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001764 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1765 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001766 logging.basicConfig(level=level,
1767 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001768 if options.config_filename and options.spec:
1769 parser.error('Cannot specifiy both --gclientfile and --spec')
1770 if not options.config_filename:
1771 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001772 options.entries_filename = options.config_filename + '_entries'
1773 if options.jobs < 1:
1774 parser.error('--jobs must be 1 or higher')
1775
1776 # These hacks need to die.
1777 if not hasattr(options, 'revisions'):
1778 # GClient.RunOnDeps expects it even if not applicable.
1779 options.revisions = []
1780 if not hasattr(options, 'head'):
1781 options.head = None
1782 if not hasattr(options, 'nohooks'):
1783 options.nohooks = True
1784 if not hasattr(options, 'deps_os'):
1785 options.deps_os = None
1786 if not hasattr(options, 'manually_grab_svn_rev'):
1787 options.manually_grab_svn_rev = None
1788 if not hasattr(options, 'force'):
1789 options.force = None
szager@chromium.org41da24b2013-05-23 19:37:04 +00001790 if options.no_nag_max:
1791 gclient_scm.SCMWrapper.nag_max = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00001792 return (options, args)
1793 parser.parse_args = Parse
1794 # We don't want wordwrapping in epilog (usually examples)
1795 parser.format_epilog = lambda _: parser.epilog or ''
1796 return parser
1797
1798
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001799def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001800 """Doesn't parse the arguments here, just find the right subcommand to
1801 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001802 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001803 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001804 '\nYour python version %s is unsupported, please upgrade.\n' %
1805 sys.version.split(' ', 1)[0])
1806 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001807 if not sys.executable:
1808 print >> sys.stderr, (
1809 '\nPython cannot find the location of it\'s own executable.\n')
1810 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001811 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001812 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001813 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1814 # operations. Python as a strong tendency to buffer sys.stdout.
1815 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001816 # Make stdout annotated with the thread ids.
1817 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001818 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001819 # Unused variable 'usage'
1820 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001821 def to_str(fn):
1822 return (
1823 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1824 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1825 cmds = (
1826 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1827 )
1828 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001829 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001830 if argv:
1831 command = Command(argv[0])
1832 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001833 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001834 GenUsage(parser, argv[0])
1835 return command(parser, argv[1:])
1836 # Not a known command. Default to help.
1837 GenUsage(parser, 'help')
1838 return CMDhelp(parser, argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001839 except KeyboardInterrupt:
1840 gclient_utils.GClientChildren.KillAllRemainingChildren()
1841 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001842 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001843 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001844 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001845
1846
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001847if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001848 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001849 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001850
1851# vim: ts=2:sw=2:tw=80:et: