blob: 2d661b30ebf01bff727b2756311fbaf305e33122 [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)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000594 file_list = [] if not options.nohooks else None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000595 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)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000615 if file_list:
616 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000617
618 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
619 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000620 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000621 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000622 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000623 continue
624 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000625 [self.root.root_dir.lower(), file_list[i].lower()])
626 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000627 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000628 while file_list[i].startswith(('\\', '/')):
629 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000630
631 # Always parse the DEPS file.
632 self.ParseDepsFile()
633
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000634 self._run_is_done(file_list or [], parsed_url)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000635
636 if self.recursion_limit:
637 # Parse the dependencies of this dependency.
638 for s in self.dependencies:
639 work_queue.enqueue(s)
640
641 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000642 if not isinstance(parsed_url, self.FileImpl):
643 # Skip file only checkout.
644 scm = gclient_scm.GetScmName(parsed_url)
645 if not options.scm or scm in options.scm:
646 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
647 # Pass in the SCM type as an env variable
648 env = os.environ.copy()
649 if scm:
650 env['GCLIENT_SCM'] = scm
651 if parsed_url:
652 env['GCLIENT_URL'] = parsed_url
ilevy@chromium.org37116242012-11-28 01:32:48 +0000653 env['GCLIENT_DEP_PATH'] = self.name
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000654 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000655 print_stdout = False
656 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000657 """Git-specific path marshaling. It is optimized for git-grep."""
658
659 def mod_path(git_pathspec):
660 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
661 modified_path = os.path.join(self.name, match.group(2))
662 branch = match.group(1) or ''
663 return '%s%s' % (branch, modified_path)
664
665 match = re.match('^Binary file ([^\0]+) matches$', line)
666 if match:
667 print 'Binary file %s matches' % mod_path(match.group(1))
668 return
669
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000670 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000671 if len(items) == 2 and items[1]:
672 print '%s : %s' % (mod_path(items[0]), items[1])
673 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000674 # Multiple null bytes or a single trailing null byte indicate
675 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000676 print '\n'.join(mod_path(path) for path in items if path)
677 else:
678 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000679 else:
680 print_stdout = True
681 filter_fn = None
682
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000683 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000684 try:
685 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000686 args, cwd=cwd, env=env, print_stdout=print_stdout,
687 filter_fn=filter_fn,
688 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000689 except subprocess2.CalledProcessError:
690 if not options.ignore:
691 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000692 else:
693 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000694
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000695
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000696 @gclient_utils.lockedmethod
697 def _run_is_done(self, file_list, parsed_url):
698 # Both these are kept for hooks that are run as a separate tree traversal.
699 self._file_list = file_list
700 self._parsed_url = parsed_url
701 self._processed = True
702
szager@google.comb9a78d32012-03-13 18:46:21 +0000703 @staticmethod
704 def GetHookAction(hook_dict, matching_file_list):
705 """Turns a parsed 'hook' dict into an executable command."""
706 logging.debug(hook_dict)
707 logging.debug(matching_file_list)
708 command = hook_dict['action'][:]
709 if command[0] == 'python':
710 # If the hook specified "python" as the first item, the action is a
711 # Python script. Run it by starting a new copy of the same
712 # interpreter.
713 command[0] = sys.executable
714 if '$matching_files' in command:
715 splice_index = command.index('$matching_files')
716 command[splice_index:splice_index + 1] = matching_file_list
717 return command
718
719 def GetHooks(self, options):
720 """Evaluates all hooks, and return them in a flat list.
721
722 RunOnDeps() must have been called before to load the DEPS.
723 """
724 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000725 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000726 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000727 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000728 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000729 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000730 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000731 # TODO(maruel): If the user is using git or git-svn, then we don't know
732 # what files have changed so we always run all hooks. It'd be nice to fix
733 # that.
734 if (options.force or
735 isinstance(self.parsed_url, self.FileImpl) or
736 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000737 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000738 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000739 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000740 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000741 # Run hooks on the basis of whether the files from the gclient operation
742 # match each hook's pattern.
743 for hook_dict in self.deps_hooks:
744 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000745 matching_file_list = [
746 f for f in self.file_list_and_children if pattern.search(f)
747 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000748 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000749 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000750 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000751 result.extend(s.GetHooks(options))
752 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000753
szager@google.comb9a78d32012-03-13 18:46:21 +0000754 def RunHooksRecursively(self, options):
755 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000756 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000757 for hook in self.GetHooks(options):
758 try:
759 gclient_utils.CheckCallAndFilterAndHeader(
760 hook, cwd=self.root.root_dir, always=True)
761 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
762 # Use a discrete exit status code of 2 to indicate that a hook action
763 # failed. Users of this script may wish to treat hook action failures
764 # differently from VC failures.
765 print >> sys.stderr, 'Error: %s' % str(e)
766 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000767
maruel@chromium.org0d812442010-08-10 12:41:08 +0000768 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000769 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000770 dependencies = self.dependencies
771 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000772 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000773 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000774 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000775 for i in d.subtree(include_all):
776 yield i
777
778 def depth_first_tree(self):
779 """Depth-first recursion including the root node."""
780 yield self
781 for i in self.dependencies:
782 for j in i.depth_first_tree():
783 if j.should_process:
784 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000785
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000786 @gclient_utils.lockedmethod
787 def add_dependency(self, new_dep):
788 self._dependencies.append(new_dep)
789
790 @gclient_utils.lockedmethod
791 def _mark_as_parsed(self, new_hooks):
792 self._deps_hooks.extend(new_hooks)
793 self._deps_parsed = True
794
maruel@chromium.org68988972011-09-20 14:11:42 +0000795 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000796 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000797 def dependencies(self):
798 return tuple(self._dependencies)
799
800 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000801 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000802 def deps_hooks(self):
803 return tuple(self._deps_hooks)
804
805 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000806 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000807 def parsed_url(self):
808 return self._parsed_url
809
810 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000811 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000812 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000813 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000814 return self._deps_parsed
815
816 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000817 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000818 def processed(self):
819 return self._processed
820
821 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000822 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000823 def hooks_ran(self):
824 return self._hooks_ran
825
826 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000827 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000828 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000829 return tuple(self._file_list)
830
831 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000832 def used_scm(self):
833 """SCMWrapper instance for this dependency or None if not processed yet."""
834 return self._used_scm
835
836 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000837 def file_list_and_children(self):
838 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000839 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000840 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000841 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000842
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000843 def __str__(self):
844 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000845 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000846 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000847 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000848 # First try the native property if it exists.
849 if hasattr(self, '_' + i):
850 value = getattr(self, '_' + i, False)
851 else:
852 value = getattr(self, i, False)
853 if value:
854 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000855
856 for d in self.dependencies:
857 out.extend([' ' + x for x in str(d).splitlines()])
858 out.append('')
859 return '\n'.join(out)
860
861 def __repr__(self):
862 return '%s: %s' % (self.name, self.url)
863
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000864 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000865 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000866 out = '%s(%s)' % (self.name, self.url)
867 i = self.parent
868 while i and i.name:
869 out = '%s(%s) -> %s' % (i.name, i.url, out)
870 i = i.parent
871 return out
872
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000873
874class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000875 """Object that represent a gclient checkout. A tree of Dependency(), one per
876 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000877
878 DEPS_OS_CHOICES = {
879 "win32": "win",
880 "win": "win",
881 "cygwin": "win",
882 "darwin": "mac",
883 "mac": "mac",
884 "unix": "unix",
885 "linux": "unix",
886 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000887 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000888 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000889 }
890
891 DEFAULT_CLIENT_FILE_TEXT = ("""\
892solutions = [
893 { "name" : "%(solution_name)s",
894 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000895 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000896 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000897 "custom_deps" : {
898 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000899 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000900 },
901]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000902cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000903""")
904
905 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
906 { "name" : "%(solution_name)s",
907 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000908 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000909 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000910 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000911%(solution_deps)s },
912 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000913 },
914""")
915
916 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
917# Snapshot generated with gclient revinfo --snapshot
918solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000919%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000920""")
921
922 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000923 # Do not change previous behavior. Only solution level and immediate DEPS
924 # are processed.
925 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000926 Dependency.__init__(self, None, None, None, None, True, None, None,
927 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000928 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000929 if options.deps_os:
930 enforced_os = options.deps_os.split(',')
931 else:
932 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
933 if 'all' in enforced_os:
934 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000935 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000936 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000937 self.config_content = None
938
939 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000940 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000941 config_dict = {}
942 self.config_content = content
943 try:
944 exec(content, config_dict)
945 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000946 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000947
peter@chromium.org1efccc82012-04-27 16:34:38 +0000948 # Append any target OS that is not already being enforced to the tuple.
949 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000950 if config_dict.get('target_os_only', False):
951 self._enforced_os = tuple(set(target_os))
952 else:
953 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
954
iannucci@chromium.org53456aa2013-07-03 19:38:34 +0000955 gclient_scm.GitWrapper.cache_dir = config_dict.get('cache_dir')
956
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000957 if not target_os and config_dict.get('target_os_only', False):
958 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
959 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +0000960
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000961 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000962 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000963 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000964 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000965 self, s['name'], s['url'],
966 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000967 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000968 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000969 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000970 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000971 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000972 except KeyError:
973 raise gclient_utils.Error('Invalid .gclient file. Solution is '
974 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000975 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
976 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000977
978 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000979 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000980 self._options.config_filename),
981 self.config_content)
982
983 @staticmethod
984 def LoadCurrentConfig(options):
985 """Searches for and loads a .gclient file relative to the current working
986 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +0000987 if options.spec:
988 client = GClient('.', options)
989 client.SetConfig(options.spec)
990 else:
991 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
992 if not path:
993 return None
994 client = GClient(path, options)
995 client.SetConfig(gclient_utils.FileRead(
996 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000997
998 if (options.revisions and
999 len(client.dependencies) > 1 and
1000 any('@' not in r for r in options.revisions)):
1001 print >> sys.stderr, (
1002 'You must specify the full solution name like --revision %s@%s\n'
1003 'when you have multiple solutions setup in your .gclient file.\n'
1004 'Other solutions present are: %s.') % (
1005 client.dependencies[0].name,
1006 options.revisions[0],
1007 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +00001008 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001009
nsylvain@google.comefc80932011-05-31 21:27:56 +00001010 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001011 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001012 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1013 'solution_name': solution_name,
1014 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001015 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001016 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001017 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001018 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001019 })
1020
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001021 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001022 """Creates a .gclient_entries file to record the list of unique checkouts.
1023
1024 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001025 """
1026 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1027 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001028 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001029 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001030 # Skip over File() dependencies as we can't version them.
1031 if not isinstance(entry.parsed_url, self.FileImpl):
1032 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1033 pprint.pformat(entry.parsed_url))
1034 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001035 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001036 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001037 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001038
1039 def _ReadEntries(self):
1040 """Read the .gclient_entries file for the given client.
1041
1042 Returns:
1043 A sequence of solution names, which will be empty if there is the
1044 entries file hasn't been created yet.
1045 """
1046 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001047 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001048 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001049 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001050 try:
1051 exec(gclient_utils.FileRead(filename), scope)
1052 except SyntaxError, e:
1053 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001054 return scope['entries']
1055
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001056 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001057 """Checks for revision overrides."""
1058 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001059 if self._options.head:
1060 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001061 # Do not check safesync_url if one or more --revision flag is specified.
1062 if not self._options.revisions:
1063 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001064 if not s.managed:
1065 self._options.revisions.append('%s@unmanaged' % s.name)
1066 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001067 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001068 if not self._options.revisions:
1069 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001070 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001071 index = 0
1072 for revision in self._options.revisions:
1073 if not '@' in revision:
1074 # Support for --revision 123
1075 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001076 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001077 if not sol in solutions_names:
1078 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1079 print >> sys.stderr, ('Please fix your script, having invalid '
1080 '--revision flags will soon considered an error.')
1081 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001082 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001083 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001084 return revision_overrides
1085
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001086 def _ApplySafeSyncRev(self, dep):
1087 """Finds a valid revision from the content of the safesync_url and apply it
1088 by appending revisions to the revision list. Throws if revision appears to
1089 be invalid for the given |dep|."""
1090 assert len(dep.safesync_url) > 0
1091 handle = urllib.urlopen(dep.safesync_url)
1092 rev = handle.read().strip()
1093 handle.close()
1094 if not rev:
1095 raise gclient_utils.Error(
1096 'It appears your safesync_url (%s) is not working properly\n'
1097 '(as it returned an empty response). Check your config.' %
1098 dep.safesync_url)
1099 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
1100 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
1101 if self._options.verbose:
1102 print('Using safesync_url revision: %s.\n' % safe_rev)
1103 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1104
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001105 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001106 """Runs a command on each dependency in a client and its dependencies.
1107
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001108 Args:
1109 command: The command to use (e.g., 'status' or 'diff')
1110 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001111 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001112 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001113 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001114 revision_overrides = {}
1115 # It's unnecessary to check for revision overrides for 'recurse'.
1116 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001117 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001118 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001119 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001120 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001121 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001122 if command in ('update', 'revert'):
1123 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001124 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001125 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001126 work_queue = gclient_utils.ExecutionQueue(
1127 self._options.jobs, pm, ignore_requirements=ignore_requirements)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001128 for s in self.dependencies:
1129 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001130 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001131
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001132 # Once all the dependencies have been processed, it's now safe to run the
1133 # hooks.
1134 if not self._options.nohooks:
1135 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001136
1137 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001138 # Notify the user if there is an orphaned entry in their working copy.
1139 # Only delete the directory if there are no changes in it, and
1140 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001141 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001142 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1143 for e in entries]
1144
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001145 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001146 if not prev_url:
1147 # entry must have been overridden via .gclient custom_deps
1148 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001149 # Fix path separator on Windows.
1150 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001151 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001152
1153 def _IsParentOfAny(parent, path_list):
1154 parent_plus_slash = parent + '/'
1155 return any(
1156 path[:len(parent_plus_slash)] == parent_plus_slash
1157 for path in path_list)
1158
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001159 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001160 if (entry not in entries and
1161 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001162 os.path.exists(e_dir)):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001163 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001164
1165 # Check to see if this directory is now part of a higher-up checkout.
1166 if scm.GetCheckoutRoot() in full_entries:
1167 logging.info('%s is part of a higher level checkout, not '
1168 'removing.', scm.GetCheckoutRoot())
1169 continue
1170
1171 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001172 scm.status(self._options, [], file_list)
1173 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001174 if (not self._options.delete_unversioned_trees or
1175 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001176 # There are modified files in this entry. Keep warning until
1177 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001178 print(('\nWARNING: \'%s\' is no longer part of this client. '
1179 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001180 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001181 else:
1182 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001183 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001184 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001185 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001186 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001187 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001188 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001189
1190 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001191 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001192 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001193 # Load all the settings.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001194 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None, False)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001195 for s in self.dependencies:
1196 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001197 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001198
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001199 def GetURLAndRev(dep):
1200 """Returns the revision-qualified SCM url for a Dependency."""
1201 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001202 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001203 if isinstance(dep.parsed_url, self.FileImpl):
1204 original_url = dep.parsed_url.file_location
1205 else:
1206 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001207 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001208 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001209 if not os.path.isdir(scm.checkout_path):
1210 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001211 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001212
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001213 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001214 new_gclient = ''
1215 # First level at .gclient
1216 for d in self.dependencies:
1217 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001218 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001219 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001220 for d in dep.dependencies:
1221 entries[d.name] = GetURLAndRev(d)
1222 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001223 GrabDeps(d)
1224 custom_deps = []
1225 for k in sorted(entries.keys()):
1226 if entries[k]:
1227 # Quotes aren't escaped...
1228 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1229 else:
1230 custom_deps.append(' \"%s\": None,\n' % k)
1231 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1232 'solution_name': d.name,
1233 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001234 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001235 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001236 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001237 'solution_deps': ''.join(custom_deps),
1238 }
1239 # Print the snapshot configuration file
1240 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001241 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001242 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001243 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001244 if self._options.actual:
1245 entries[d.name] = GetURLAndRev(d)
1246 else:
1247 entries[d.name] = d.parsed_url
1248 keys = sorted(entries.keys())
1249 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001250 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001251 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001252
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001253 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001254 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001255 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001256
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001257 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001258 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001259 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001260 return self._root_dir
1261
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001262 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001263 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001264 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001265 return self._enforced_os
1266
maruel@chromium.org68988972011-09-20 14:11:42 +00001267 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001268 def recursion_limit(self):
1269 """How recursive can each dependencies in DEPS file can load DEPS file."""
1270 return self._recursion_limit
1271
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001272 @property
1273 def target_os(self):
1274 return self._enforced_os
1275
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001276
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001277#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001278
1279
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001280def CMDcleanup(parser, args):
1281 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001282
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001283Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001284"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001285 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1286 help='override deps for the specified (comma-separated) '
1287 'platform(s); \'all\' will process all deps_os '
1288 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001289 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001290 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001291 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001292 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001293 if options.verbose:
1294 # Print out the .gclient file. This is longer than if we just printed the
1295 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001296 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001297 return client.RunOnDeps('cleanup', args)
1298
1299
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001300@attr('usage', '[command] [args ...]')
1301def CMDrecurse(parser, args):
1302 """Operates on all the entries.
1303
1304 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001305 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1306 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001307 """
1308 # Stop parsing at the first non-arg so that these go through to the command
1309 parser.disable_interspersed_args()
1310 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001311 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001312 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001313 help='Ignore non-zero return codes from subcommands.')
1314 parser.add_option('--prepend-dir', action='store_true',
1315 help='Prepend relative dir for use with git <cmd> --null.')
1316 parser.add_option('--no-progress', action='store_true',
1317 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001318 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001319 if not args:
1320 print >> sys.stderr, 'Need to supply a command!'
1321 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001322 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1323 if not root_and_entries:
1324 print >> sys.stderr, (
1325 'You need to run gclient sync at least once to use \'recurse\'.\n'
1326 'This is because .gclient_entries needs to exist and be up to date.')
1327 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001328
1329 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001330 scm_set = set()
1331 for scm in options.scm:
1332 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001333 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001334
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001335 options.nohooks = True
1336 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001337 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1338 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001339
1340
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001341@attr('usage', '[args ...]')
1342def CMDfetch(parser, args):
1343 """Fetches upstream commits for all modules.
1344
1345Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1346"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001347 (options, args) = parser.parse_args(args)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001348 return CMDrecurse(Parser(), [
1349 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1350
1351
1352def CMDgrep(parser, args):
1353 """Greps through git repos managed by gclient.
1354
1355Runs 'git grep [args...]' for each module.
1356"""
1357
1358 # We can't use optparse because it will try to parse arguments sent
1359 # to git grep and throw an error. :-(
1360 if not args or re.match('(-h|--help)$', args[0]):
1361 print >> sys.stderr, (
1362 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1363 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1364 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1365 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1366 ' end of your query.'
1367 )
1368 return 1
1369
1370 jobs_arg = ['--jobs=1']
1371 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1372 jobs_arg, args = args[:1], args[1:]
1373 elif re.match(r'(-j|--jobs)$', args[0]):
1374 jobs_arg, args = args[:2], args[2:]
1375
1376 return CMDrecurse(
1377 parser,
1378 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1379 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001380
1381
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001382@attr('usage', '[url] [safesync url]')
1383def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001384 """Create a .gclient file in the current directory.
1385
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001386This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001387top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001388modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001389provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001390URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001391"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001392
1393 # We do a little dance with the --gclientfile option. 'gclient config' is the
1394 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1395 # arguments. So, we temporarily stash any --gclientfile parameter into
1396 # options.output_config_file until after the (gclientfile xor spec) error
1397 # check.
1398 parser.remove_option('--gclientfile')
1399 parser.add_option('--gclientfile', dest='output_config_file',
1400 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001401 parser.add_option('--name',
1402 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001403 parser.add_option('--deps-file', default='DEPS',
1404 help='overrides the default name for the DEPS file for the'
1405 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001406 parser.add_option('--unmanaged', action='store_true', default=False,
1407 help='overrides the default behavior to make it possible '
1408 'to have the main solution untouched by gclient '
1409 '(gclient will check out unmanaged dependencies but '
1410 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001411 parser.add_option('--git-deps', action='store_true',
1412 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001413 parser.add_option('--cache-dir',
1414 help='(git only) Cache all git repos into this dir and do '
1415 'shared clones from the cache, instead of cloning '
1416 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001417 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001418 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001419 if options.output_config_file:
1420 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001421 if ((options.spec and args) or len(args) > 2 or
1422 (not options.spec and not args)):
1423 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1424
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001425 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001426 if options.spec:
1427 client.SetConfig(options.spec)
1428 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001429 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001430 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001431 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001432 if name.endswith('.git'):
1433 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001434 else:
1435 # specify an alternate relpath for the given URL.
1436 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001437 deps_file = options.deps_file
1438 if options.git_deps:
1439 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001440 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001441 if len(args) > 1:
1442 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001443 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001444 managed=not options.unmanaged,
1445 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001446 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001447 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001448
1449
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001450@attr('epilog', """Example:
1451 gclient pack > patch.txt
1452 generate simple patch for configured client and dependences
1453""")
1454def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001455 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001456
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001457Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001458dependencies, and performs minimal postprocessing of the output. The
1459resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001460checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001461"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001462 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1463 help='override deps for the specified (comma-separated) '
1464 'platform(s); \'all\' will process all deps_os '
1465 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001466 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001467 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001468 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001469 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001470 client = GClient.LoadCurrentConfig(options)
1471 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001472 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001473 if options.verbose:
1474 # Print out the .gclient file. This is longer than if we just printed the
1475 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001476 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001477 return client.RunOnDeps('pack', args)
1478
1479
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001480def CMDstatus(parser, args):
1481 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001482 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1483 help='override deps for the specified (comma-separated) '
1484 'platform(s); \'all\' will process all deps_os '
1485 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001486 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001487 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001488 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001489 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001490 if options.verbose:
1491 # Print out the .gclient file. This is longer than if we just printed the
1492 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001493 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001494 return client.RunOnDeps('status', args)
1495
1496
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001497@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001498 gclient sync
1499 update files from SCM according to current configuration,
1500 *for modules which have changed since last update or sync*
1501 gclient sync --force
1502 update files from SCM according to current configuration, for
1503 all modules (useful for recovering files deleted from local copy)
1504 gclient sync --revision src@31000
1505 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001506""")
1507def CMDsync(parser, args):
1508 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001509 parser.add_option('-f', '--force', action='store_true',
1510 help='force update even for unchanged modules')
1511 parser.add_option('-n', '--nohooks', action='store_true',
1512 help='don\'t run hooks after the update is complete')
1513 parser.add_option('-r', '--revision', action='append',
1514 dest='revisions', metavar='REV', default=[],
1515 help='Enforces revision/hash for the solutions with the '
1516 'format src@rev. The src@ part is optional and can be '
1517 'skipped. -r can be used multiple times when .gclient '
1518 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001519 'if the src@ part is skipped. Note that specifying '
1520 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001521 parser.add_option('--with_branch_heads', action='store_true',
1522 help='Clone git "branch_heads" refspecs in addition to '
1523 'the default refspecs. This adds about 1/2GB to a '
1524 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001525 parser.add_option('-t', '--transitive', action='store_true',
1526 help='When a revision is specified (in the DEPS file or '
1527 'with the command-line flag), transitively update '
1528 'the dependencies to the date of the given revision. '
1529 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001530 parser.add_option('-H', '--head', action='store_true',
1531 help='skips any safesync_urls specified in '
1532 'configured solutions and sync to head instead')
1533 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001534 help='Deletes from the working copy any dependencies that '
1535 'have been removed since the last sync, as long as '
1536 'there are no local modifications. When used with '
1537 '--force, such dependencies are removed even if they '
1538 'have local modifications. When used with --reset, '
1539 'all untracked directories are removed from the '
1540 'working copy, exclusing those which are explicitly '
1541 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001542 parser.add_option('-R', '--reset', action='store_true',
1543 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001544 parser.add_option('-M', '--merge', action='store_true',
1545 help='merge upstream changes instead of trying to '
1546 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001547 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1548 help='override deps for the specified (comma-separated) '
1549 'platform(s); \'all\' will process all deps_os '
1550 'references')
1551 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1552 help='Skip svn up whenever possible by requesting '
1553 'actual HEAD revision from the repository')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001554 parser.add_option('--upstream', action='store_true',
1555 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001556 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001557 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001558
1559 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001560 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001561
maruel@chromium.org307d1792010-05-31 20:03:13 +00001562 if options.revisions and options.head:
1563 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001564 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001565
1566 if options.verbose:
1567 # Print out the .gclient file. This is longer than if we just printed the
1568 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001569 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001570 return client.RunOnDeps('update', args)
1571
1572
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001573def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001574 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001575 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001576
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001577def CMDdiff(parser, args):
1578 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001579 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1580 help='override deps for the specified (comma-separated) '
1581 'platform(s); \'all\' will process all deps_os '
1582 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001583 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001584 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001585 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001586 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001587 if options.verbose:
1588 # Print out the .gclient file. This is longer than if we just printed the
1589 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001590 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001591 return client.RunOnDeps('diff', args)
1592
1593
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001594def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001595 """Revert all modifications in every dependencies.
1596
1597 That's the nuclear option to get back to a 'clean' state. It removes anything
1598 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001599 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1600 help='override deps for the specified (comma-separated) '
1601 'platform(s); \'all\' will process all deps_os '
1602 'references')
1603 parser.add_option('-n', '--nohooks', action='store_true',
1604 help='don\'t run hooks after the revert is complete')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001605 parser.add_option('--upstream', action='store_true',
1606 help='Make repo state match upstream branch.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001607 (options, args) = parser.parse_args(args)
1608 # --force is implied.
1609 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001610 options.reset = False
1611 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001612 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001613 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001614 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001615 return client.RunOnDeps('revert', args)
1616
1617
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001618def CMDrunhooks(parser, args):
1619 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001620 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1621 help='override deps for the specified (comma-separated) '
1622 'platform(s); \'all\' will process all deps_os '
1623 'references')
1624 parser.add_option('-f', '--force', action='store_true', default=True,
1625 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001626 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001627 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001628 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001629 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001630 if options.verbose:
1631 # Print out the .gclient file. This is longer than if we just printed the
1632 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001633 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001634 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001635 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001636 return client.RunOnDeps('runhooks', args)
1637
1638
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001639def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001640 """Output revision info mapping for the client and its dependencies.
1641
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001642 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001643 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001644 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1645 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001646 commit can change.
1647 """
1648 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1649 help='override deps for the specified (comma-separated) '
1650 'platform(s); \'all\' will process all deps_os '
1651 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001652 parser.add_option('-a', '--actual', action='store_true',
1653 help='gets the actual checked out revisions instead of the '
1654 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001655 parser.add_option('-s', '--snapshot', action='store_true',
1656 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001657 'version of all repositories to reproduce the tree, '
1658 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001659 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001660 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001661 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001662 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001663 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001664 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001665
1666
szager@google.comb9a78d32012-03-13 18:46:21 +00001667def CMDhookinfo(parser, args):
1668 """Output the hooks that would be run by `gclient runhooks`"""
1669 (options, args) = parser.parse_args(args)
1670 options.force = True
1671 client = GClient.LoadCurrentConfig(options)
1672 if not client:
1673 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1674 client.RunOnDeps(None, [])
1675 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1676 return 0
1677
1678
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001679def Command(name):
1680 return getattr(sys.modules[__name__], 'CMD' + name, None)
1681
1682
1683def CMDhelp(parser, args):
1684 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001685 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001686 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001687 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001688 parser.print_help()
1689 return 0
1690
1691
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001692def GenUsage(parser, command):
1693 """Modify an OptParse object with the function's documentation."""
1694 obj = Command(command)
1695 if command == 'help':
1696 command = '<command>'
1697 # OptParser.description prefer nicely non-formatted strings.
1698 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1699 usage = getattr(obj, 'usage', '')
1700 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1701 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001702
1703
maruel@chromium.org0895b752011-08-26 20:40:33 +00001704def Parser():
1705 """Returns the default parser."""
1706 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001707 # some arm boards have issues with parallel sync.
1708 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001709 jobs = 1
1710 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001711 jobs = max(8, gclient_utils.NumLocalCpus())
cmp@chromium.org3b37d342013-06-19 19:14:25 +00001712 # cmp: 2013/06/19
1713 # Temporary workaround to lower bot-load on SVN server.
1714 if os.environ.get('CHROME_HEADLESS') == '1':
ilevy@chromium.org413ffbc2013-06-20 02:35:38 +00001715 jobs = 4
szager@chromium.orge2e03202012-07-31 18:05:16 +00001716 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001717 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001718 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001719 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001720 parser.add_option('-v', '--verbose', action='count', default=0,
1721 help='Produces additional output for diagnostics. Can be '
1722 'used up to three times for more logging info.')
1723 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001724 default=None,
1725 help='Specify an alternate %s file' % gclientfile_default)
1726 parser.add_option('--spec',
1727 default=None,
1728 help='create a gclient file containing the provided '
1729 'string. Due to Cygwin/Python brokenness, it '
1730 'probably can\'t contain any newlines.')
szager@chromium.org41da24b2013-05-23 19:37:04 +00001731 parser.add_option('--no-nag-max', default=False, action='store_true',
1732 help='If a subprocess runs for too long without generating'
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001733 ' terminal output, generate warnings, but do not kill'
1734 ' the process.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001735 # Integrate standard options processing.
1736 old_parser = parser.parse_args
1737 def Parse(args):
1738 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001739 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1740 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001741 logging.basicConfig(level=level,
1742 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001743 if options.config_filename and options.spec:
1744 parser.error('Cannot specifiy both --gclientfile and --spec')
1745 if not options.config_filename:
1746 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001747 options.entries_filename = options.config_filename + '_entries'
1748 if options.jobs < 1:
1749 parser.error('--jobs must be 1 or higher')
1750
1751 # These hacks need to die.
1752 if not hasattr(options, 'revisions'):
1753 # GClient.RunOnDeps expects it even if not applicable.
1754 options.revisions = []
1755 if not hasattr(options, 'head'):
1756 options.head = None
1757 if not hasattr(options, 'nohooks'):
1758 options.nohooks = True
1759 if not hasattr(options, 'deps_os'):
1760 options.deps_os = None
1761 if not hasattr(options, 'manually_grab_svn_rev'):
1762 options.manually_grab_svn_rev = None
1763 if not hasattr(options, 'force'):
1764 options.force = None
szager@chromium.org41da24b2013-05-23 19:37:04 +00001765 if options.no_nag_max:
1766 gclient_scm.SCMWrapper.nag_max = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00001767 return (options, args)
1768 parser.parse_args = Parse
1769 # We don't want wordwrapping in epilog (usually examples)
1770 parser.format_epilog = lambda _: parser.epilog or ''
1771 return parser
1772
1773
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001774def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001775 """Doesn't parse the arguments here, just find the right subcommand to
1776 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001777 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001778 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001779 '\nYour python version %s is unsupported, please upgrade.\n' %
1780 sys.version.split(' ', 1)[0])
1781 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001782 if not sys.executable:
1783 print >> sys.stderr, (
1784 '\nPython cannot find the location of it\'s own executable.\n')
1785 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001786 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001787 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001788 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1789 # operations. Python as a strong tendency to buffer sys.stdout.
1790 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001791 # Make stdout annotated with the thread ids.
1792 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001793 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001794 # Unused variable 'usage'
1795 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001796 def to_str(fn):
1797 return (
1798 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1799 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1800 cmds = (
1801 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1802 )
1803 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001804 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001805 if argv:
1806 command = Command(argv[0])
1807 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001808 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001809 GenUsage(parser, argv[0])
1810 return command(parser, argv[1:])
1811 # Not a known command. Default to help.
1812 GenUsage(parser, 'help')
1813 return CMDhelp(parser, argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00001814 except KeyboardInterrupt:
1815 gclient_utils.GClientChildren.KillAllRemainingChildren()
1816 raise
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001817 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001818 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001819 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001820
1821
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001822if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001823 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001824 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001825
1826# vim: ts=2:sw=2:tw=80:et: