blob: f7c9cc18be88abb0aedd3913e1ab223d608c34d4 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
peter@chromium.org1efccc82012-04-27 16:34:38 +000050
51Specifying a target OS
52 An optional key named "target_os" may be added to a gclient file to specify
53 one or more additional operating systems that should be considered when
54 processing the deps_os dict of a DEPS file.
55
56 Example:
57 target_os = [ "android" ]
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +000058
59 If the "target_os_only" key is also present and true, then *only* the
60 operating systems listed in "target_os" will be used.
61
62 Example:
63 target_os = [ "ios" ]
64 target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000065"""
66
maruel@chromium.org82798cb2012-02-23 18:16:12 +000067__version__ = "0.6.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000068
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000069import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000070import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071import optparse
72import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000073import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000074import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000075import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000079import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000081import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000082
maruel@chromium.org35625c72011-03-23 17:34:02 +000083import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000084import gclient_scm
85import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000086from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000087import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +000088from third_party import colorama
89# Import shortcut.
90from third_party.colorama import Fore
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000091
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000093def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000094 """Sets an attribute on a function."""
95 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000096 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000097 return fn
98 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000099
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000100
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000101## GClient implementation.
102
103
maruel@chromium.org116704f2010-06-11 17:34:38 +0000104class GClientKeywords(object):
105 class FromImpl(object):
106 """Used to implement the From() syntax."""
107
108 def __init__(self, module_name, sub_target_name=None):
109 """module_name is the dep module we want to include from. It can also be
110 the name of a subdirectory to include from.
111
112 sub_target_name is an optional parameter if the module name in the other
113 DEPS file is different. E.g., you might want to map src/net to net."""
114 self.module_name = module_name
115 self.sub_target_name = sub_target_name
116
117 def __str__(self):
118 return 'From(%s, %s)' % (repr(self.module_name),
119 repr(self.sub_target_name))
120
maruel@chromium.org116704f2010-06-11 17:34:38 +0000121 class FileImpl(object):
122 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000123 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000124
125 def __init__(self, file_location):
126 self.file_location = file_location
127
128 def __str__(self):
129 return 'File("%s")' % self.file_location
130
131 def GetPath(self):
132 return os.path.split(self.file_location)[0]
133
134 def GetFilename(self):
135 rev_tokens = self.file_location.split('@')
136 return os.path.split(rev_tokens[0])[1]
137
138 def GetRevision(self):
139 rev_tokens = self.file_location.split('@')
140 if len(rev_tokens) > 1:
141 return rev_tokens[1]
142 return None
143
144 class VarImpl(object):
145 def __init__(self, custom_vars, local_scope):
146 self._custom_vars = custom_vars
147 self._local_scope = local_scope
148
149 def Lookup(self, var_name):
150 """Implements the Var syntax."""
151 if var_name in self._custom_vars:
152 return self._custom_vars[var_name]
153 elif var_name in self._local_scope.get("vars", {}):
154 return self._local_scope["vars"][var_name]
155 raise gclient_utils.Error("Var is not defined: %s" % var_name)
156
157
maruel@chromium.org064186c2011-09-27 23:53:33 +0000158class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000159 """Immutable configuration settings."""
160 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000161 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000162 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000163 GClientKeywords.__init__(self)
164
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000165 # These are not mutable:
166 self._parent = parent
167 self._safesync_url = safesync_url
168 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000169 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000170 # 'managed' determines whether or not this dependency is synced/updated by
171 # gclient after gclient checks it out initially. The difference between
172 # 'managed' and 'should_process' is that the user specifies 'managed' via
173 # the --unmanaged command-line flag or a .gclient config, where
174 # 'should_process' is dynamically set by gclient if it goes over its
175 # recursion limit and controls gclient's behavior so it does not misbehave.
176 self._managed = managed
177 self._should_process = should_process
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000178 # This is a mutable value that overrides the normal recursion limit for this
179 # dependency. It is read from the actual DEPS file so cannot be set on
180 # class instantiation.
181 self.recursion_override = None
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000182 # This is a mutable value which has the list of 'target_os' OSes listed in
183 # the current deps file.
184 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000185
186 # These are only set in .gclient and not in DEPS files.
187 self._custom_vars = custom_vars or {}
188 self._custom_deps = custom_deps or {}
189
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000190 # TODO(iannucci): Remove this when all masters are correctly substituting
191 # the new blink url.
192 if (self._custom_vars.get('webkit_trunk', '') ==
193 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'):
iannucci@chromium.org50395ea2013-04-04 04:47:42 +0000194 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk'
195 print 'Overwriting Var("webkit_trunk") with %s' % new_url
196 self._custom_vars['webkit_trunk'] = new_url
iannucci@chromium.org3e8df4b2013-04-04 01:08:52 +0000197
maruel@chromium.org064186c2011-09-27 23:53:33 +0000198 # Post process the url to remove trailing slashes.
199 if isinstance(self._url, basestring):
200 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
201 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000202 self._url = self._url.replace('/@', '@')
203 elif not isinstance(self._url,
204 (self.FromImpl, self.FileImpl, None.__class__)):
205 raise gclient_utils.Error(
206 ('dependency url must be either a string, None, '
207 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000208 # Make any deps_file path platform-appropriate.
209 for sep in ['/', '\\']:
210 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211
212 @property
213 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 return self._deps_file
215
216 @property
217 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218 return self._managed
219
220 @property
221 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000222 return self._parent
223
224 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000225 def root(self):
226 """Returns the root node, a GClient object."""
227 if not self.parent:
228 # This line is to signal pylint that it could be a GClient instance.
229 return self or GClient(None, None)
230 return self.parent.root
231
232 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000233 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000234 return self._safesync_url
235
236 @property
237 def should_process(self):
238 """True if this dependency should be processed, i.e. checked out."""
239 return self._should_process
240
241 @property
242 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 return self._custom_vars.copy()
244
245 @property
246 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000247 return self._custom_deps.copy()
248
maruel@chromium.org064186c2011-09-27 23:53:33 +0000249 @property
250 def url(self):
251 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000252
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000253 @property
254 def recursion_limit(self):
255 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000256 if self.recursion_override is not None:
257 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000258 return max(self.parent.recursion_limit - 1, 0)
259
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000260 @property
261 def target_os(self):
262 if self.local_target_os is not None:
263 return tuple(set(self.local_target_os).union(self.parent.target_os))
264 else:
265 return self.parent.target_os
266
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000267 def get_custom_deps(self, name, url):
268 """Returns a custom deps if applicable."""
269 if self.parent:
270 url = self.parent.get_custom_deps(name, url)
271 # None is a valid return value to disable a dependency.
272 return self.custom_deps.get(name, url)
273
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274
275class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000276 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000277
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000278 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000279 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000280 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000281 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000282 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000283 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000284
285 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000286 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000287
288 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000289 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000290 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000291 # A cache of the files affected by the current operation, necessary for
292 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000293 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000294 # If it is not set to True, the dependency wasn't processed for its child
295 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000296 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000297 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000298 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000299 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000300 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000301 # This is the scm used to checkout self.url. It may be used by dependencies
302 # to get the datetime of the revision we checked out.
303 self._used_scm = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000304
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000305 if not self.name and self.parent:
306 raise gclient_utils.Error('Dependency without name')
307
maruel@chromium.org470b5432011-10-11 18:18:19 +0000308 @property
309 def requirements(self):
310 """Calculate the list of requirements."""
311 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000312 # self.parent is implicitly a requirement. This will be recursive by
313 # definition.
314 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000315 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000316
317 # For a tree with at least 2 levels*, the leaf node needs to depend
318 # on the level higher up in an orderly way.
319 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
320 # thus unsorted, while the .gclient format is a list thus sorted.
321 #
322 # * _recursion_limit is hard coded 2 and there is no hope to change this
323 # value.
324 #
325 # Interestingly enough, the following condition only works in the case we
326 # want: self is a 2nd level node. 3nd level node wouldn't need this since
327 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000328 if self.parent and self.parent.parent and not self.parent.parent.parent:
329 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000330
331 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000332 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000333
maruel@chromium.org470b5432011-10-11 18:18:19 +0000334 if self.name:
335 requirements |= set(
336 obj.name for obj in self.root.subtree(False)
337 if (obj is not self
338 and obj.name and
339 self.name.startswith(posixpath.join(obj.name, ''))))
340 requirements = tuple(sorted(requirements))
341 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
342 return requirements
343
344 def verify_validity(self):
345 """Verifies that this Dependency is fine to add as a child of another one.
346
347 Returns True if this entry should be added, False if it is a duplicate of
348 another entry.
349 """
350 logging.info('Dependency(%s).verify_validity()' % self.name)
351 if self.name in [s.name for s in self.parent.dependencies]:
352 raise gclient_utils.Error(
353 'The same name "%s" appears multiple times in the deps section' %
354 self.name)
355 if not self.should_process:
356 # Return early, no need to set requirements.
357 return True
358
359 # This require a full tree traversal with locks.
360 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
361 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000362 self_url = self.LateOverride(self.url)
363 sibling_url = sibling.LateOverride(sibling.url)
364 # Allow to have only one to be None or ''.
365 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000366 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000367 ('Dependency %s specified more than once:\n'
368 ' %s [%s]\n'
369 'vs\n'
370 ' %s [%s]') % (
371 self.name,
372 sibling.hierarchy(),
373 sibling_url,
374 self.hierarchy(),
375 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000376 # In theory we could keep it as a shadow of the other one. In
377 # practice, simply ignore it.
378 logging.warn('Won\'t process duplicate dependency %s' % sibling)
379 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000380 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000381
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000382 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000383 """Resolves the parsed url from url.
384
385 Manages From() keyword accordingly. Do not touch self.parsed_url nor
386 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000387 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000388 parsed_url = self.get_custom_deps(self.name, url)
389 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000390 logging.info(
391 'Dependency(%s).LateOverride(%s) -> %s' %
392 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000393 return parsed_url
394
395 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000396 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000397 ref = [
398 dep for dep in self.root.subtree(True) if url.module_name == dep.name
399 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000400 if not ref:
401 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
402 url.module_name, ref))
403 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000404 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000405 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000406 found_deps = [d for d in ref.dependencies if d.name == sub_target]
407 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000408 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000409 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
410 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000411 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000412
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000413 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000414 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000415 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000416 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000417 'Dependency(%s).LateOverride(%s) -> %s (From)' %
418 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000419 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000420
421 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000422 parsed_url = urlparse.urlparse(url)
423 if not parsed_url[0]:
424 # A relative url. Fetch the real base.
425 path = parsed_url[2]
426 if not path.startswith('/'):
427 raise gclient_utils.Error(
428 'relative DEPS entry \'%s\' must begin with a slash' % url)
429 # Create a scm just to query the full url.
430 parent_url = self.parent.parsed_url
431 if isinstance(parent_url, self.FileImpl):
432 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000433 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000434 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000435 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000436 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000437 logging.info(
438 'Dependency(%s).LateOverride(%s) -> %s' %
439 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000440 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000441
442 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000443 logging.info(
444 'Dependency(%s).LateOverride(%s) -> %s (File)' %
445 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000446 return url
447
448 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000449 logging.info(
450 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000451 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000452
453 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000454
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000455 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000456 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000457 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000458 assert not self.dependencies
459 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000460 local_scope = {}
461 var = self.VarImpl(self.custom_vars, local_scope)
462 global_scope = {
463 'File': self.FileImpl,
464 'From': self.FromImpl,
465 'Var': var.Lookup,
466 'deps_os': {},
467 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000468 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000469 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000470 logging.info(
471 'ParseDepsFile(%s): No %s file found at %s' % (
472 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000473 else:
474 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000475 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000476 # Eval the content.
477 try:
478 exec(deps_content, global_scope, local_scope)
479 except SyntaxError, e:
480 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000481 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000482 if 'recursion' in local_scope:
483 self.recursion_override = local_scope.get('recursion')
484 logging.warning(
485 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000486 # If present, save 'target_os' in the local_target_os property.
487 if 'target_os' in local_scope:
488 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000489 # load os specific dependencies if defined. these dependencies may
490 # override or extend the values defined by the 'deps' member.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000491 target_os_deps = {}
maruel@chromium.org271375b2010-06-23 19:17:38 +0000492 if 'deps_os' in local_scope:
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000493 for deps_os_key in self.target_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000494 os_deps = local_scope['deps_os'].get(deps_os_key, {})
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000495 if len(self.target_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000496 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000497 # platform, so we collect the broadest set of dependencies
498 # available. We may end up with the wrong revision of something for
499 # our platform, but this is the best we can do.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000500 target_os_deps.update(
501 [x for x in os_deps.items() if not x[0] in target_os_deps])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000502 else:
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000503 target_os_deps.update(os_deps)
504
505 # deps_os overrides paths from deps
506 deps.update(target_os_deps)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000507
maruel@chromium.org271375b2010-06-23 19:17:38 +0000508 # If a line is in custom_deps, but not in the solution, we want to append
509 # this line to the solution.
510 for d in self.custom_deps:
511 if d not in deps:
512 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000513
514 # If use_relative_paths is set in the DEPS file, regenerate
515 # the dictionary using paths relative to the directory containing
516 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000517 use_relative_paths = local_scope.get('use_relative_paths', False)
518 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000519 rel_deps = {}
520 for d, url in deps.items():
521 # normpath is required to allow DEPS to use .. in their
522 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000523 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
524 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000525
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000526 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000527 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000528 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000529 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000530 deps_to_add.append(Dependency(
531 self, name, url, None, None, None, None,
532 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000533 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000534 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
535 logging.info('ParseDepsFile(%s) done' % self.name)
536
537 def add_dependencies_and_close(self, deps_to_add, hooks):
538 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000539 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000540 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000541 self.add_dependency(dep)
542 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000543
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000544 def maybeGetParentRevision(
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000545 self, command, options, parsed_url, parent_name, revision_overrides):
546 """Uses revision/timestamp of parent if no explicit revision was specified.
547
548 If we are performing an update and --transitive is set, use
549 - the parent's revision if 'self.url' is in the same repository
550 - the parent's timestamp otherwise
551 to update 'self.url'. The used revision/timestamp will be set in
552 'options.revision'.
553 If we have an explicit revision do nothing.
554 """
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000555 if command == 'update' and options.transitive and not options.revision:
556 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
557 if not revision:
558 options.revision = revision_overrides.get(parent_name)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000559 if (options.revision and
560 not gclient_utils.IsDateRevision(options.revision)):
561 assert self.parent and self.parent.used_scm
562 # If this dependency is in the same repository as parent it's url will
563 # start with a slash. If so we take the parent revision instead of
564 # it's timestamp.
565 # (The timestamps of commits in google code are broken -- which can
566 # result in dependencies to be checked out at the wrong revision)
567 if self.url.startswith('/'):
568 if options.verbose:
569 print('Using parent\'s revision %s since we are in the same '
570 'repository.' % options.revision)
571 else:
572 parent_revision_date = self.parent.used_scm.GetRevisionDate(
573 options.revision)
574 options.revision = gclient_utils.MakeDateRevision(
575 parent_revision_date)
576 if options.verbose:
577 print('Using parent\'s revision date %s since we are in a '
578 'different repository.' % options.revision)
579 revision_overrides[self.name] = options.revision
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000580
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000581 # Arguments number differs from overridden method
582 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000583 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000584 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000585 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000586 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000587 if not self.should_process:
588 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000589 # When running runhooks, there's no need to consult the SCM.
590 # All known hooks are expected to run unconditionally regardless of working
591 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000592 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000593 parsed_url = self.LateOverride(self.url)
594 file_list = []
595 if run_scm and parsed_url:
596 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000597 # Special support for single-file checkout.
598 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000599 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
600 # pylint: disable=E1103
601 options.revision = parsed_url.GetRevision()
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000602 self._used_scm = gclient_scm.SVNWrapper(
603 parsed_url.GetPath(), self.root.root_dir, self.name)
604 self._used_scm.RunCommand('updatesingle',
605 options, args + [parsed_url.GetFilename()], file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000606 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000607 # Create a shallow copy to mutate revision.
608 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000609 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000610 self.maybeGetParentRevision(
611 command, options, parsed_url, self.parent.name, revision_overrides)
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000612 self._used_scm = gclient_scm.CreateSCM(
613 parsed_url, self.root.root_dir, self.name)
614 self._used_scm.RunCommand(command, options, args, file_list)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000615 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000616
617 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
618 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000619 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000620 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000621 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000622 continue
623 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000624 [self.root.root_dir.lower(), file_list[i].lower()])
625 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000626 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000627 while file_list[i].startswith(('\\', '/')):
628 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000629
630 # Always parse the DEPS file.
631 self.ParseDepsFile()
632
633 self._run_is_done(file_list, parsed_url)
634
635 if self.recursion_limit:
636 # Parse the dependencies of this dependency.
637 for s in self.dependencies:
638 work_queue.enqueue(s)
639
640 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000641 if not isinstance(parsed_url, self.FileImpl):
642 # Skip file only checkout.
643 scm = gclient_scm.GetScmName(parsed_url)
644 if not options.scm or scm in options.scm:
645 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
646 # Pass in the SCM type as an env variable
647 env = os.environ.copy()
648 if scm:
649 env['GCLIENT_SCM'] = scm
650 if parsed_url:
651 env['GCLIENT_URL'] = parsed_url
ilevy@chromium.org37116242012-11-28 01:32:48 +0000652 env['GCLIENT_DEP_PATH'] = self.name
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000653 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000654 print_stdout = False
655 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000656 """Git-specific path marshaling. It is optimized for git-grep."""
657
658 def mod_path(git_pathspec):
659 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
660 modified_path = os.path.join(self.name, match.group(2))
661 branch = match.group(1) or ''
662 return '%s%s' % (branch, modified_path)
663
664 match = re.match('^Binary file ([^\0]+) matches$', line)
665 if match:
666 print 'Binary file %s matches' % mod_path(match.group(1))
667 return
668
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000669 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000670 if len(items) == 2 and items[1]:
671 print '%s : %s' % (mod_path(items[0]), items[1])
672 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000673 # Multiple null bytes or a single trailing null byte indicate
674 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000675 print '\n'.join(mod_path(path) for path in items if path)
676 else:
677 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000678 else:
679 print_stdout = True
680 filter_fn = None
681
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000682 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000683 try:
684 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000685 args, cwd=cwd, env=env, print_stdout=print_stdout,
686 filter_fn=filter_fn,
687 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000688 except subprocess2.CalledProcessError:
689 if not options.ignore:
690 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000691 else:
692 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000693
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000694
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000695 @gclient_utils.lockedmethod
696 def _run_is_done(self, file_list, parsed_url):
697 # Both these are kept for hooks that are run as a separate tree traversal.
698 self._file_list = file_list
699 self._parsed_url = parsed_url
700 self._processed = True
701
szager@google.comb9a78d32012-03-13 18:46:21 +0000702 @staticmethod
703 def GetHookAction(hook_dict, matching_file_list):
704 """Turns a parsed 'hook' dict into an executable command."""
705 logging.debug(hook_dict)
706 logging.debug(matching_file_list)
707 command = hook_dict['action'][:]
708 if command[0] == 'python':
709 # If the hook specified "python" as the first item, the action is a
710 # Python script. Run it by starting a new copy of the same
711 # interpreter.
712 command[0] = sys.executable
713 if '$matching_files' in command:
714 splice_index = command.index('$matching_files')
715 command[splice_index:splice_index + 1] = matching_file_list
716 return command
717
718 def GetHooks(self, options):
719 """Evaluates all hooks, and return them in a flat list.
720
721 RunOnDeps() must have been called before to load the DEPS.
722 """
723 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000724 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000725 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000726 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000727 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000728 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000729 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000730 # TODO(maruel): If the user is using git or git-svn, then we don't know
731 # what files have changed so we always run all hooks. It'd be nice to fix
732 # that.
733 if (options.force or
734 isinstance(self.parsed_url, self.FileImpl) or
735 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000736 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000737 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000738 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000739 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000740 # Run hooks on the basis of whether the files from the gclient operation
741 # match each hook's pattern.
742 for hook_dict in self.deps_hooks:
743 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000744 matching_file_list = [
745 f for f in self.file_list_and_children if pattern.search(f)
746 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000747 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000748 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000749 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000750 result.extend(s.GetHooks(options))
751 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000752
szager@google.comb9a78d32012-03-13 18:46:21 +0000753 def RunHooksRecursively(self, options):
754 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000755 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000756 for hook in self.GetHooks(options):
757 try:
758 gclient_utils.CheckCallAndFilterAndHeader(
759 hook, cwd=self.root.root_dir, always=True)
760 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
761 # Use a discrete exit status code of 2 to indicate that a hook action
762 # failed. Users of this script may wish to treat hook action failures
763 # differently from VC failures.
764 print >> sys.stderr, 'Error: %s' % str(e)
765 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000766
maruel@chromium.org0d812442010-08-10 12:41:08 +0000767 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000768 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000769 dependencies = self.dependencies
770 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000771 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000772 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000773 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000774 for i in d.subtree(include_all):
775 yield i
776
777 def depth_first_tree(self):
778 """Depth-first recursion including the root node."""
779 yield self
780 for i in self.dependencies:
781 for j in i.depth_first_tree():
782 if j.should_process:
783 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000784
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000785 @gclient_utils.lockedmethod
786 def add_dependency(self, new_dep):
787 self._dependencies.append(new_dep)
788
789 @gclient_utils.lockedmethod
790 def _mark_as_parsed(self, new_hooks):
791 self._deps_hooks.extend(new_hooks)
792 self._deps_parsed = True
793
maruel@chromium.org68988972011-09-20 14:11:42 +0000794 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000795 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000796 def dependencies(self):
797 return tuple(self._dependencies)
798
799 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000800 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000801 def deps_hooks(self):
802 return tuple(self._deps_hooks)
803
804 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000805 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000806 def parsed_url(self):
807 return self._parsed_url
808
809 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000810 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000811 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000812 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000813 return self._deps_parsed
814
815 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000816 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000817 def processed(self):
818 return self._processed
819
820 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000821 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000822 def hooks_ran(self):
823 return self._hooks_ran
824
825 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000826 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000827 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000828 return tuple(self._file_list)
829
830 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000831 def used_scm(self):
832 """SCMWrapper instance for this dependency or None if not processed yet."""
833 return self._used_scm
834
835 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000836 def file_list_and_children(self):
837 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000838 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000839 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000840 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000841
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000842 def __str__(self):
843 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000844 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000845 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000846 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000847 # First try the native property if it exists.
848 if hasattr(self, '_' + i):
849 value = getattr(self, '_' + i, False)
850 else:
851 value = getattr(self, i, False)
852 if value:
853 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000854
855 for d in self.dependencies:
856 out.extend([' ' + x for x in str(d).splitlines()])
857 out.append('')
858 return '\n'.join(out)
859
860 def __repr__(self):
861 return '%s: %s' % (self.name, self.url)
862
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000863 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000864 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000865 out = '%s(%s)' % (self.name, self.url)
866 i = self.parent
867 while i and i.name:
868 out = '%s(%s) -> %s' % (i.name, i.url, out)
869 i = i.parent
870 return out
871
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000872
873class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000874 """Object that represent a gclient checkout. A tree of Dependency(), one per
875 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000876
877 DEPS_OS_CHOICES = {
878 "win32": "win",
879 "win": "win",
880 "cygwin": "win",
881 "darwin": "mac",
882 "mac": "mac",
883 "unix": "unix",
884 "linux": "unix",
885 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000886 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000887 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000888 }
889
890 DEFAULT_CLIENT_FILE_TEXT = ("""\
891solutions = [
892 { "name" : "%(solution_name)s",
893 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000894 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000895 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000896 "custom_deps" : {
897 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000898 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000899 },
900]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000901cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000902""")
903
904 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
905 { "name" : "%(solution_name)s",
906 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000907 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000908 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000909 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000910%(solution_deps)s },
911 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000912 },
913""")
914
915 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
916# Snapshot generated with gclient revinfo --snapshot
917solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000918%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000919""")
920
921 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000922 # Do not change previous behavior. Only solution level and immediate DEPS
923 # are processed.
924 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000925 Dependency.__init__(self, None, None, None, None, True, None, None,
926 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000927 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000928 if options.deps_os:
929 enforced_os = options.deps_os.split(',')
930 else:
931 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
932 if 'all' in enforced_os:
933 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000934 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000935 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000936 self.config_content = None
937
938 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000939 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000940 config_dict = {}
941 self.config_content = content
942 try:
943 exec(content, config_dict)
944 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000945 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000946
peter@chromium.org1efccc82012-04-27 16:34:38 +0000947 # Append any target OS that is not already being enforced to the tuple.
948 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000949 if config_dict.get('target_os_only', False):
950 self._enforced_os = tuple(set(target_os))
951 else:
952 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
953
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000954 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
955
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000956 if not target_os and config_dict.get('target_os_only', False):
957 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
958 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +0000959
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000960 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000961 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000962 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000963 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000964 self, s['name'], s['url'],
965 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000966 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000967 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000968 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000969 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000970 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000971 except KeyError:
972 raise gclient_utils.Error('Invalid .gclient file. Solution is '
973 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000974 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
975 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000976
977 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000978 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000979 self._options.config_filename),
980 self.config_content)
981
982 @staticmethod
983 def LoadCurrentConfig(options):
984 """Searches for and loads a .gclient file relative to the current working
985 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +0000986 if options.spec:
987 client = GClient('.', options)
988 client.SetConfig(options.spec)
989 else:
990 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
991 if not path:
992 return None
993 client = GClient(path, options)
994 client.SetConfig(gclient_utils.FileRead(
995 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000996
997 if (options.revisions and
998 len(client.dependencies) > 1 and
999 any('@' not in r for r in options.revisions)):
1000 print >> sys.stderr, (
1001 'You must specify the full solution name like --revision %s@%s\n'
1002 'when you have multiple solutions setup in your .gclient file.\n'
1003 'Other solutions present are: %s.') % (
1004 client.dependencies[0].name,
1005 options.revisions[0],
1006 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001007 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001008
nsylvain@google.comefc80932011-05-31 21:27:56 +00001009 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001010 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001011 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1012 'solution_name': solution_name,
1013 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001014 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001015 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001016 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001017 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001018 })
1019
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001020 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001021 """Creates a .gclient_entries file to record the list of unique checkouts.
1022
1023 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001024 """
1025 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1026 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001027 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001028 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001029 # Skip over File() dependencies as we can't version them.
1030 if not isinstance(entry.parsed_url, self.FileImpl):
1031 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1032 pprint.pformat(entry.parsed_url))
1033 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001034 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001035 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001036 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001037
1038 def _ReadEntries(self):
1039 """Read the .gclient_entries file for the given client.
1040
1041 Returns:
1042 A sequence of solution names, which will be empty if there is the
1043 entries file hasn't been created yet.
1044 """
1045 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001046 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001047 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001048 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001049 try:
1050 exec(gclient_utils.FileRead(filename), scope)
1051 except SyntaxError, e:
1052 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001053 return scope['entries']
1054
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001055 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001056 """Checks for revision overrides."""
1057 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001058 if self._options.head:
1059 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001060 # Do not check safesync_url if one or more --revision flag is specified.
1061 if not self._options.revisions:
1062 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001063 if not s.managed:
1064 self._options.revisions.append('%s@unmanaged' % s.name)
1065 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001066 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001067 if not self._options.revisions:
1068 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001069 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001070 index = 0
1071 for revision in self._options.revisions:
1072 if not '@' in revision:
1073 # Support for --revision 123
1074 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001075 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001076 if not sol in solutions_names:
1077 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1078 print >> sys.stderr, ('Please fix your script, having invalid '
1079 '--revision flags will soon considered an error.')
1080 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001081 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001082 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001083 return revision_overrides
1084
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001085 def _ApplySafeSyncRev(self, dep):
1086 """Finds a valid revision from the content of the safesync_url and apply it
1087 by appending revisions to the revision list. Throws if revision appears to
1088 be invalid for the given |dep|."""
1089 assert len(dep.safesync_url) > 0
1090 handle = urllib.urlopen(dep.safesync_url)
1091 rev = handle.read().strip()
1092 handle.close()
1093 if not rev:
1094 raise gclient_utils.Error(
1095 'It appears your safesync_url (%s) is not working properly\n'
1096 '(as it returned an empty response). Check your config.' %
1097 dep.safesync_url)
1098 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
1099 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
1100 if self._options.verbose:
1101 print('Using safesync_url revision: %s.\n' % safe_rev)
1102 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1103
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001104 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001105 """Runs a command on each dependency in a client and its dependencies.
1106
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001107 Args:
1108 command: The command to use (e.g., 'status' or 'diff')
1109 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001110 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001111 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001112 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001113 revision_overrides = {}
1114 # It's unnecessary to check for revision overrides for 'recurse'.
1115 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001116 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001117 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001118 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001119 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001120 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001121 if command in ('update', 'revert'):
1122 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001123 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001124 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001125 work_queue = gclient_utils.ExecutionQueue(
1126 self._options.jobs, pm, ignore_requirements=ignore_requirements)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001127 for s in self.dependencies:
1128 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001129 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001130
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001131 # Once all the dependencies have been processed, it's now safe to run the
1132 # hooks.
1133 if not self._options.nohooks:
1134 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135
1136 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001137 # Notify the user if there is an orphaned entry in their working copy.
1138 # Only delete the directory if there are no changes in it, and
1139 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001140 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001141 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1142 for e in entries]
1143
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001144 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001145 if not prev_url:
1146 # entry must have been overridden via .gclient custom_deps
1147 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001148 # Fix path separator on Windows.
1149 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001150 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001151
1152 def _IsParentOfAny(parent, path_list):
1153 parent_plus_slash = parent + '/'
1154 return any(
1155 path[:len(parent_plus_slash)] == parent_plus_slash
1156 for path in path_list)
1157
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001158 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001159 if (entry not in entries and
1160 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001161 os.path.exists(e_dir)):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001162 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001163
1164 # Check to see if this directory is now part of a higher-up checkout.
1165 if scm.GetCheckoutRoot() in full_entries:
1166 logging.info('%s is part of a higher level checkout, not '
1167 'removing.', scm.GetCheckoutRoot())
1168 continue
1169
1170 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001171 scm.status(self._options, [], file_list)
1172 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001173 if (not self._options.delete_unversioned_trees or
1174 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001175 # There are modified files in this entry. Keep warning until
1176 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001177 print(('\nWARNING: \'%s\' is no longer part of this client. '
1178 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001179 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001180 else:
1181 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001182 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001183 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001184 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001185 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001186 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001187 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188
1189 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001190 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001191 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001192 # Load all the settings.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001193 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None, False)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001194 for s in self.dependencies:
1195 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001196 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001197
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001198 def GetURLAndRev(dep):
1199 """Returns the revision-qualified SCM url for a Dependency."""
1200 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001201 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001202 if isinstance(dep.parsed_url, self.FileImpl):
1203 original_url = dep.parsed_url.file_location
1204 else:
1205 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001206 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001207 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001208 if not os.path.isdir(scm.checkout_path):
1209 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001210 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001211
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001212 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001213 new_gclient = ''
1214 # First level at .gclient
1215 for d in self.dependencies:
1216 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001217 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001218 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001219 for d in dep.dependencies:
1220 entries[d.name] = GetURLAndRev(d)
1221 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001222 GrabDeps(d)
1223 custom_deps = []
1224 for k in sorted(entries.keys()):
1225 if entries[k]:
1226 # Quotes aren't escaped...
1227 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1228 else:
1229 custom_deps.append(' \"%s\": None,\n' % k)
1230 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1231 'solution_name': d.name,
1232 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001233 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001234 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001235 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001236 'solution_deps': ''.join(custom_deps),
1237 }
1238 # Print the snapshot configuration file
1239 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001240 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001241 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001242 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001243 if self._options.actual:
1244 entries[d.name] = GetURLAndRev(d)
1245 else:
1246 entries[d.name] = d.parsed_url
1247 keys = sorted(entries.keys())
1248 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001249 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001250 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001251
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001252 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001253 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001254 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001255
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001256 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001257 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001258 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001259 return self._root_dir
1260
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001261 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001262 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001263 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001264 return self._enforced_os
1265
maruel@chromium.org68988972011-09-20 14:11:42 +00001266 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001267 def recursion_limit(self):
1268 """How recursive can each dependencies in DEPS file can load DEPS file."""
1269 return self._recursion_limit
1270
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001271 @property
1272 def target_os(self):
1273 return self._enforced_os
1274
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001275
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001276#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001277
1278
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001279def CMDcleanup(parser, args):
1280 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001281
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001282Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001283"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001284 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1285 help='override deps for the specified (comma-separated) '
1286 'platform(s); \'all\' will process all deps_os '
1287 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001288 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001289 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001290 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001291 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001292 if options.verbose:
1293 # Print out the .gclient file. This is longer than if we just printed the
1294 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001295 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001296 return client.RunOnDeps('cleanup', args)
1297
1298
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001299@attr('usage', '[command] [args ...]')
1300def CMDrecurse(parser, args):
1301 """Operates on all the entries.
1302
1303 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001304 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1305 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001306 """
1307 # Stop parsing at the first non-arg so that these go through to the command
1308 parser.disable_interspersed_args()
1309 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001310 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001311 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001312 help='Ignore non-zero return codes from subcommands.')
1313 parser.add_option('--prepend-dir', action='store_true',
1314 help='Prepend relative dir for use with git <cmd> --null.')
1315 parser.add_option('--no-progress', action='store_true',
1316 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001317 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001318 if not args:
1319 print >> sys.stderr, 'Need to supply a command!'
1320 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001321 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1322 if not root_and_entries:
1323 print >> sys.stderr, (
1324 'You need to run gclient sync at least once to use \'recurse\'.\n'
1325 'This is because .gclient_entries needs to exist and be up to date.')
1326 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001327
1328 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001329 scm_set = set()
1330 for scm in options.scm:
1331 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001332 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001333
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001334 options.nohooks = True
1335 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001336 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1337 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001338
1339
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001340@attr('usage', '[args ...]')
1341def CMDfetch(parser, args):
1342 """Fetches upstream commits for all modules.
1343
1344Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1345"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001346 (options, args) = parser.parse_args(args)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001347 return CMDrecurse(Parser(), [
1348 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1349
1350
1351def CMDgrep(parser, args):
1352 """Greps through git repos managed by gclient.
1353
1354Runs 'git grep [args...]' for each module.
1355"""
1356
1357 # We can't use optparse because it will try to parse arguments sent
1358 # to git grep and throw an error. :-(
1359 if not args or re.match('(-h|--help)$', args[0]):
1360 print >> sys.stderr, (
1361 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1362 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1363 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1364 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1365 ' end of your query.'
1366 )
1367 return 1
1368
1369 jobs_arg = ['--jobs=1']
1370 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1371 jobs_arg, args = args[:1], args[1:]
1372 elif re.match(r'(-j|--jobs)$', args[0]):
1373 jobs_arg, args = args[:2], args[2:]
1374
1375 return CMDrecurse(
1376 parser,
1377 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1378 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001379
1380
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001381@attr('usage', '[url] [safesync url]')
1382def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001383 """Create a .gclient file in the current directory.
1384
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001385This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001386top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001387modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001388provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001389URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001390"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001391
1392 # We do a little dance with the --gclientfile option. 'gclient config' is the
1393 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1394 # arguments. So, we temporarily stash any --gclientfile parameter into
1395 # options.output_config_file until after the (gclientfile xor spec) error
1396 # check.
1397 parser.remove_option('--gclientfile')
1398 parser.add_option('--gclientfile', dest='output_config_file',
1399 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001400 parser.add_option('--name',
1401 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001402 parser.add_option('--deps-file', default='DEPS',
1403 help='overrides the default name for the DEPS file for the'
1404 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001405 parser.add_option('--unmanaged', action='store_true', default=False,
1406 help='overrides the default behavior to make it possible '
1407 'to have the main solution untouched by gclient '
1408 '(gclient will check out unmanaged dependencies but '
1409 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001410 parser.add_option('--git-deps', action='store_true',
1411 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001412 parser.add_option('--cache-dir',
1413 help='(git only) Cache all git repos into this dir and do '
1414 'shared clones from the cache, instead of cloning '
1415 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001416 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001417 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001418 if options.output_config_file:
1419 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001420 if ((options.spec and args) or len(args) > 2 or
1421 (not options.spec and not args)):
1422 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1423
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001424 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001425 if options.spec:
1426 client.SetConfig(options.spec)
1427 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001428 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001429 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001430 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001431 if name.endswith('.git'):
1432 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001433 else:
1434 # specify an alternate relpath for the given URL.
1435 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001436 deps_file = options.deps_file
1437 if options.git_deps:
1438 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001439 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001440 if len(args) > 1:
1441 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001442 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001443 managed=not options.unmanaged,
1444 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001445 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001446 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001447
1448
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001449@attr('epilog', """Example:
1450 gclient pack > patch.txt
1451 generate simple patch for configured client and dependences
1452""")
1453def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001454 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001455
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001456Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001457dependencies, and performs minimal postprocessing of the output. The
1458resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001459checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001460"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001461 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1462 help='override deps for the specified (comma-separated) '
1463 'platform(s); \'all\' will process all deps_os '
1464 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001465 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001466 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001467 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001468 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001469 client = GClient.LoadCurrentConfig(options)
1470 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001471 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001472 if options.verbose:
1473 # Print out the .gclient file. This is longer than if we just printed the
1474 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001475 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001476 return client.RunOnDeps('pack', args)
1477
1478
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001479def CMDstatus(parser, args):
1480 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001481 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1482 help='override deps for the specified (comma-separated) '
1483 'platform(s); \'all\' will process all deps_os '
1484 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001485 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001486 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001487 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001488 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001489 if options.verbose:
1490 # Print out the .gclient file. This is longer than if we just printed the
1491 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001492 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001493 return client.RunOnDeps('status', args)
1494
1495
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001496@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001497 gclient sync
1498 update files from SCM according to current configuration,
1499 *for modules which have changed since last update or sync*
1500 gclient sync --force
1501 update files from SCM according to current configuration, for
1502 all modules (useful for recovering files deleted from local copy)
1503 gclient sync --revision src@31000
1504 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001505""")
1506def CMDsync(parser, args):
1507 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001508 parser.add_option('-f', '--force', action='store_true',
1509 help='force update even for unchanged modules')
1510 parser.add_option('-n', '--nohooks', action='store_true',
1511 help='don\'t run hooks after the update is complete')
1512 parser.add_option('-r', '--revision', action='append',
1513 dest='revisions', metavar='REV', default=[],
1514 help='Enforces revision/hash for the solutions with the '
1515 'format src@rev. The src@ part is optional and can be '
1516 'skipped. -r can be used multiple times when .gclient '
1517 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001518 'if the src@ part is skipped. Note that specifying '
1519 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001520 parser.add_option('--with_branch_heads', action='store_true',
1521 help='Clone git "branch_heads" refspecs in addition to '
1522 'the default refspecs. This adds about 1/2GB to a '
1523 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001524 parser.add_option('-t', '--transitive', action='store_true',
1525 help='When a revision is specified (in the DEPS file or '
1526 'with the command-line flag), transitively update '
1527 'the dependencies to the date of the given revision. '
1528 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001529 parser.add_option('-H', '--head', action='store_true',
1530 help='skips any safesync_urls specified in '
1531 'configured solutions and sync to head instead')
1532 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001533 help='Deletes from the working copy any dependencies that '
1534 'have been removed since the last sync, as long as '
1535 'there are no local modifications. When used with '
1536 '--force, such dependencies are removed even if they '
1537 'have local modifications. When used with --reset, '
1538 'all untracked directories are removed from the '
1539 'working copy, exclusing those which are explicitly '
1540 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001541 parser.add_option('-R', '--reset', action='store_true',
1542 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001543 parser.add_option('-M', '--merge', action='store_true',
1544 help='merge upstream changes instead of trying to '
1545 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001546 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1547 help='override deps for the specified (comma-separated) '
1548 'platform(s); \'all\' will process all deps_os '
1549 'references')
1550 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1551 help='Skip svn up whenever possible by requesting '
1552 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001553 parser.add_option('--upstream', action='store_true',
1554 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001555 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001556 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001557
1558 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001559 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001560
maruel@chromium.org307d1792010-05-31 20:03:13 +00001561 if options.revisions and options.head:
1562 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001563 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001564
1565 if options.verbose:
1566 # Print out the .gclient file. This is longer than if we just printed the
1567 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001568 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001569 return client.RunOnDeps('update', args)
1570
1571
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001572def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001573 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001574 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001575
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001576def CMDdiff(parser, args):
1577 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001578 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1579 help='override deps for the specified (comma-separated) '
1580 'platform(s); \'all\' will process all deps_os '
1581 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001582 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001583 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001584 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 if options.verbose:
1587 # Print out the .gclient file. This is longer than if we just printed the
1588 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001589 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001590 return client.RunOnDeps('diff', args)
1591
1592
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001593def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001594 """Revert all modifications in every dependencies.
1595
1596 That's the nuclear option to get back to a 'clean' state. It removes anything
1597 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001598 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1599 help='override deps for the specified (comma-separated) '
1600 'platform(s); \'all\' will process all deps_os '
1601 'references')
1602 parser.add_option('-n', '--nohooks', action='store_true',
1603 help='don\'t run hooks after the revert is complete')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001604 parser.add_option('--upstream', action='store_true',
1605 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001606 (options, args) = parser.parse_args(args)
1607 # --force is implied.
1608 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001609 options.reset = False
1610 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001611 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001612 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001613 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001614 return client.RunOnDeps('revert', args)
1615
1616
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001617def CMDrunhooks(parser, args):
1618 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001619 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1620 help='override deps for the specified (comma-separated) '
1621 'platform(s); \'all\' will process all deps_os '
1622 'references')
1623 parser.add_option('-f', '--force', action='store_true', default=True,
1624 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001625 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001626 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001627 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001628 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001629 if options.verbose:
1630 # Print out the .gclient file. This is longer than if we just printed the
1631 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001632 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001633 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001634 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001635 return client.RunOnDeps('runhooks', args)
1636
1637
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001638def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001639 """Output revision info mapping for the client and its dependencies.
1640
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001641 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001642 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001643 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1644 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001645 commit can change.
1646 """
1647 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1648 help='override deps for the specified (comma-separated) '
1649 'platform(s); \'all\' will process all deps_os '
1650 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001651 parser.add_option('-a', '--actual', action='store_true',
1652 help='gets the actual checked out revisions instead of the '
1653 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001654 parser.add_option('-s', '--snapshot', action='store_true',
1655 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001656 'version of all repositories to reproduce the tree, '
1657 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001658 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001659 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001660 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001661 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001662 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001663 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001664
1665
szager@google.comb9a78d32012-03-13 18:46:21 +00001666def CMDhookinfo(parser, args):
1667 """Output the hooks that would be run by `gclient runhooks`"""
1668 (options, args) = parser.parse_args(args)
1669 options.force = True
1670 client = GClient.LoadCurrentConfig(options)
1671 if not client:
1672 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1673 client.RunOnDeps(None, [])
1674 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1675 return 0
1676
1677
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001678def Command(name):
1679 return getattr(sys.modules[__name__], 'CMD' + name, None)
1680
1681
1682def CMDhelp(parser, args):
1683 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001684 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001685 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001686 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001687 parser.print_help()
1688 return 0
1689
1690
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001691def GenUsage(parser, command):
1692 """Modify an OptParse object with the function's documentation."""
1693 obj = Command(command)
1694 if command == 'help':
1695 command = '<command>'
1696 # OptParser.description prefer nicely non-formatted strings.
1697 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1698 usage = getattr(obj, 'usage', '')
1699 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1700 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001701
1702
maruel@chromium.org0895b752011-08-26 20:40:33 +00001703def Parser():
1704 """Returns the default parser."""
1705 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001706 # some arm boards have issues with parallel sync.
1707 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001708 jobs = 1
1709 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001710 jobs = max(8, gclient_utils.NumLocalCpus())
cmp@chromium.org3b37d342013-06-19 19:14:25 +00001711 # cmp: 2013/06/19
1712 # Temporary workaround to lower bot-load on SVN server.
1713 if os.environ.get('CHROME_HEADLESS') == '1':
ilevy@chromium.org413ffbc2013-06-20 02:35:38 +00001714 jobs = 4
szager@chromium.orge2e03202012-07-31 18:05:16 +00001715 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001716 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001717 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001718 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001719 parser.add_option('-v', '--verbose', action='count', default=0,
1720 help='Produces additional output for diagnostics. Can be '
1721 'used up to three times for more logging info.')
1722 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001723 default=None,
1724 help='Specify an alternate %s file' % gclientfile_default)
1725 parser.add_option('--spec',
1726 default=None,
1727 help='create a gclient file containing the provided '
1728 'string. Due to Cygwin/Python brokenness, it '
1729 'probably can\'t contain any newlines.')
szager@chromium.org41da24b2013-05-23 19:37:04 +00001730 parser.add_option('--no-nag-max', default=False, action='store_true',
1731 help='If a subprocess runs for too long without generating'
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001732 ' terminal output, generate warnings, but do not kill'
1733 ' the process.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001734 # Integrate standard options processing.
1735 old_parser = parser.parse_args
1736 def Parse(args):
1737 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001738 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1739 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001740 logging.basicConfig(level=level,
1741 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001742 if options.config_filename and options.spec:
1743 parser.error('Cannot specifiy both --gclientfile and --spec')
1744 if not options.config_filename:
1745 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001746 options.entries_filename = options.config_filename + '_entries'
1747 if options.jobs < 1:
1748 parser.error('--jobs must be 1 or higher')
1749
1750 # These hacks need to die.
1751 if not hasattr(options, 'revisions'):
1752 # GClient.RunOnDeps expects it even if not applicable.
1753 options.revisions = []
1754 if not hasattr(options, 'head'):
1755 options.head = None
1756 if not hasattr(options, 'nohooks'):
1757 options.nohooks = True
1758 if not hasattr(options, 'deps_os'):
1759 options.deps_os = None
1760 if not hasattr(options, 'manually_grab_svn_rev'):
1761 options.manually_grab_svn_rev = None
1762 if not hasattr(options, 'force'):
1763 options.force = None
szager@chromium.org41da24b2013-05-23 19:37:04 +00001764 if options.no_nag_max:
1765 gclient_scm.SCMWrapper.nag_max = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00001766 return (options, args)
1767 parser.parse_args = Parse
1768 # We don't want wordwrapping in epilog (usually examples)
1769 parser.format_epilog = lambda _: parser.epilog or ''
1770 return parser
1771
1772
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001773def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001774 """Doesn't parse the arguments here, just find the right subcommand to
1775 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001776 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001777 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001778 '\nYour python version %s is unsupported, please upgrade.\n' %
1779 sys.version.split(' ', 1)[0])
1780 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001781 if not sys.executable:
1782 print >> sys.stderr, (
1783 '\nPython cannot find the location of it\'s own executable.\n')
1784 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001785 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001786 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001787 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1788 # operations. Python as a strong tendency to buffer sys.stdout.
1789 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001790 # Make stdout annotated with the thread ids.
1791 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001792 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001793 # Unused variable 'usage'
1794 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001795 def to_str(fn):
1796 return (
1797 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1798 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1799 cmds = (
1800 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1801 )
1802 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001803 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001804 if argv:
1805 command = Command(argv[0])
1806 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001807 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001808 GenUsage(parser, argv[0])
1809 return command(parser, argv[1:])
1810 # Not a known command. Default to help.
1811 GenUsage(parser, 'help')
1812 return CMDhelp(parser, argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001813 except KeyboardInterrupt:
1814 gclient_utils.GClientChildren.KillAllRemainingChildren()
1815 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001816 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001817 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001818 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001819
1820
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001821if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001822 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001823 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001824
1825# vim: ts=2:sw=2:tw=80:et: