blob: c7613aeec52e0a6dc7accc35883f908e95872db9 [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'):
194 self._custom_vars['webkit_trunk'] = (
195 'svn://svn-mirror.golo.chromium.org/blink/trunk')
196
maruel@chromium.org064186c2011-09-27 23:53:33 +0000197 # Post process the url to remove trailing slashes.
198 if isinstance(self._url, basestring):
199 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
200 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000201 self._url = self._url.replace('/@', '@')
202 elif not isinstance(self._url,
203 (self.FromImpl, self.FileImpl, None.__class__)):
204 raise gclient_utils.Error(
205 ('dependency url must be either a string, None, '
206 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000207 # Make any deps_file path platform-appropriate.
208 for sep in ['/', '\\']:
209 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000210
211 @property
212 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000213 return self._deps_file
214
215 @property
216 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000217 return self._managed
218
219 @property
220 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000221 return self._parent
222
223 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000224 def root(self):
225 """Returns the root node, a GClient object."""
226 if not self.parent:
227 # This line is to signal pylint that it could be a GClient instance.
228 return self or GClient(None, None)
229 return self.parent.root
230
231 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000232 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000233 return self._safesync_url
234
235 @property
236 def should_process(self):
237 """True if this dependency should be processed, i.e. checked out."""
238 return self._should_process
239
240 @property
241 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 return self._custom_vars.copy()
243
244 @property
245 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000246 return self._custom_deps.copy()
247
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 @property
249 def url(self):
250 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000251
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000252 @property
253 def recursion_limit(self):
254 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000255 if self.recursion_override is not None:
256 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000257 return max(self.parent.recursion_limit - 1, 0)
258
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000259 @property
260 def target_os(self):
261 if self.local_target_os is not None:
262 return tuple(set(self.local_target_os).union(self.parent.target_os))
263 else:
264 return self.parent.target_os
265
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000266 def get_custom_deps(self, name, url):
267 """Returns a custom deps if applicable."""
268 if self.parent:
269 url = self.parent.get_custom_deps(name, url)
270 # None is a valid return value to disable a dependency.
271 return self.custom_deps.get(name, url)
272
maruel@chromium.org064186c2011-09-27 23:53:33 +0000273
274class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000275 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000276
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000277 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000278 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000279 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000280 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000281 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000282 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000283
284 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000285 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000286
287 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000288 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000289 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000290 # A cache of the files affected by the current operation, necessary for
291 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000292 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000293 # If it is not set to True, the dependency wasn't processed for its child
294 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000295 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000296 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000297 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000298 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000299 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000300
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000301 if not self.name and self.parent:
302 raise gclient_utils.Error('Dependency without name')
303
maruel@chromium.org470b5432011-10-11 18:18:19 +0000304 @property
305 def requirements(self):
306 """Calculate the list of requirements."""
307 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000308 # self.parent is implicitly a requirement. This will be recursive by
309 # definition.
310 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000311 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000312
313 # For a tree with at least 2 levels*, the leaf node needs to depend
314 # on the level higher up in an orderly way.
315 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
316 # thus unsorted, while the .gclient format is a list thus sorted.
317 #
318 # * _recursion_limit is hard coded 2 and there is no hope to change this
319 # value.
320 #
321 # Interestingly enough, the following condition only works in the case we
322 # want: self is a 2nd level node. 3nd level node wouldn't need this since
323 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000324 if self.parent and self.parent.parent and not self.parent.parent.parent:
325 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000326
327 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000328 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000329
maruel@chromium.org470b5432011-10-11 18:18:19 +0000330 if self.name:
331 requirements |= set(
332 obj.name for obj in self.root.subtree(False)
333 if (obj is not self
334 and obj.name and
335 self.name.startswith(posixpath.join(obj.name, ''))))
336 requirements = tuple(sorted(requirements))
337 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
338 return requirements
339
340 def verify_validity(self):
341 """Verifies that this Dependency is fine to add as a child of another one.
342
343 Returns True if this entry should be added, False if it is a duplicate of
344 another entry.
345 """
346 logging.info('Dependency(%s).verify_validity()' % self.name)
347 if self.name in [s.name for s in self.parent.dependencies]:
348 raise gclient_utils.Error(
349 'The same name "%s" appears multiple times in the deps section' %
350 self.name)
351 if not self.should_process:
352 # Return early, no need to set requirements.
353 return True
354
355 # This require a full tree traversal with locks.
356 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
357 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000358 self_url = self.LateOverride(self.url)
359 sibling_url = sibling.LateOverride(sibling.url)
360 # Allow to have only one to be None or ''.
361 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000362 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000363 ('Dependency %s specified more than once:\n'
364 ' %s [%s]\n'
365 'vs\n'
366 ' %s [%s]') % (
367 self.name,
368 sibling.hierarchy(),
369 sibling_url,
370 self.hierarchy(),
371 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000372 # In theory we could keep it as a shadow of the other one. In
373 # practice, simply ignore it.
374 logging.warn('Won\'t process duplicate dependency %s' % sibling)
375 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000376 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000377
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000378 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000379 """Resolves the parsed url from url.
380
381 Manages From() keyword accordingly. Do not touch self.parsed_url nor
382 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000383 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000384 parsed_url = self.get_custom_deps(self.name, url)
385 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000386 logging.info(
387 'Dependency(%s).LateOverride(%s) -> %s' %
388 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000389 return parsed_url
390
391 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000392 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000393 ref = [
394 dep for dep in self.root.subtree(True) if url.module_name == dep.name
395 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000396 if not ref:
397 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
398 url.module_name, ref))
399 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000400 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000401 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000402 found_deps = [d for d in ref.dependencies if d.name == sub_target]
403 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000404 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000405 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
406 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000407 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000408
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000409 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000410 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000411 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000412 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000413 'Dependency(%s).LateOverride(%s) -> %s (From)' %
414 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000415 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000416
417 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000418 parsed_url = urlparse.urlparse(url)
419 if not parsed_url[0]:
420 # A relative url. Fetch the real base.
421 path = parsed_url[2]
422 if not path.startswith('/'):
423 raise gclient_utils.Error(
424 'relative DEPS entry \'%s\' must begin with a slash' % url)
425 # Create a scm just to query the full url.
426 parent_url = self.parent.parsed_url
427 if isinstance(parent_url, self.FileImpl):
428 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000429 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000430 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000431 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000432 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000433 logging.info(
434 'Dependency(%s).LateOverride(%s) -> %s' %
435 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000436 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000437
438 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000439 logging.info(
440 'Dependency(%s).LateOverride(%s) -> %s (File)' %
441 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000442 return url
443
444 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000445 logging.info(
446 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000447 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000448
449 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000450
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000451 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000452 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000453 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000454 assert not self.dependencies
455 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000456 local_scope = {}
457 var = self.VarImpl(self.custom_vars, local_scope)
458 global_scope = {
459 'File': self.FileImpl,
460 'From': self.FromImpl,
461 'Var': var.Lookup,
462 'deps_os': {},
463 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000464 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000465 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000466 logging.info(
467 'ParseDepsFile(%s): No %s file found at %s' % (
468 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000469 else:
470 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000471 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000472 # Eval the content.
473 try:
474 exec(deps_content, global_scope, local_scope)
475 except SyntaxError, e:
476 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000477 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000478 if 'recursion' in local_scope:
479 self.recursion_override = local_scope.get('recursion')
480 logging.warning(
481 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000482 # If present, save 'target_os' in the local_target_os property.
483 if 'target_os' in local_scope:
484 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000485 # load os specific dependencies if defined. these dependencies may
486 # override or extend the values defined by the 'deps' member.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000487 target_os_deps = {}
maruel@chromium.org271375b2010-06-23 19:17:38 +0000488 if 'deps_os' in local_scope:
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000489 for deps_os_key in self.target_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000490 os_deps = local_scope['deps_os'].get(deps_os_key, {})
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000491 if len(self.target_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000492 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000493 # platform, so we collect the broadest set of dependencies
494 # available. We may end up with the wrong revision of something for
495 # our platform, but this is the best we can do.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000496 target_os_deps.update(
497 [x for x in os_deps.items() if not x[0] in target_os_deps])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000498 else:
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000499 target_os_deps.update(os_deps)
500
501 # deps_os overrides paths from deps
502 deps.update(target_os_deps)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000503
maruel@chromium.org271375b2010-06-23 19:17:38 +0000504 # If a line is in custom_deps, but not in the solution, we want to append
505 # this line to the solution.
506 for d in self.custom_deps:
507 if d not in deps:
508 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000509
510 # If use_relative_paths is set in the DEPS file, regenerate
511 # the dictionary using paths relative to the directory containing
512 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000513 use_relative_paths = local_scope.get('use_relative_paths', False)
514 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000515 rel_deps = {}
516 for d, url in deps.items():
517 # normpath is required to allow DEPS to use .. in their
518 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000519 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
520 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000521
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000522 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000523 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000524 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000525 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000526 deps_to_add.append(Dependency(
527 self, name, url, None, None, None, None,
528 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000529 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000530 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
531 logging.info('ParseDepsFile(%s) done' % self.name)
532
533 def add_dependencies_and_close(self, deps_to_add, hooks):
534 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000535 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000536 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000537 self.add_dependency(dep)
538 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000539
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000540 @staticmethod
541 def maybeGetParentRevision(
542 command, options, parsed_url, parent_name, revision_overrides):
543 """If we are performing an update and --transitive is set, set the
544 revision to the parent's revision. If we have an explicit revision
545 do nothing."""
546 if command == 'update' and options.transitive and not options.revision:
547 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
548 if not revision:
549 options.revision = revision_overrides.get(parent_name)
550 if options.verbose and options.revision:
551 print("Using parent's revision date: %s" % options.revision)
552 # If the parent has a revision override, then it must have been
553 # converted to date format.
554 assert (not options.revision or
555 gclient_utils.IsDateRevision(options.revision))
556
557 @staticmethod
558 def maybeConvertToDateRevision(
559 command, options, name, scm, revision_overrides):
560 """If we are performing an update and --transitive is set, convert the
561 revision to a date-revision (if necessary). Instead of having
562 -r 101 replace the revision with the time stamp of 101 (e.g.
563 "{2011-18-04}").
564 This way dependencies are upgraded to the revision they had at the
565 check-in of revision 101."""
566 if (command == 'update' and
567 options.transitive and
568 options.revision and
569 not gclient_utils.IsDateRevision(options.revision)):
570 revision_date = scm.GetRevisionDate(options.revision)
571 revision = gclient_utils.MakeDateRevision(revision_date)
572 if options.verbose:
573 print("Updating revision override from %s to %s." %
574 (options.revision, revision))
575 revision_overrides[name] = revision
576
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000577 # Arguments number differs from overridden method
578 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000579 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000580 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000581 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000582 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000583 if not self.should_process:
584 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000585 # When running runhooks, there's no need to consult the SCM.
586 # All known hooks are expected to run unconditionally regardless of working
587 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000588 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000589 parsed_url = self.LateOverride(self.url)
590 file_list = []
591 if run_scm and parsed_url:
592 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000593 # Special support for single-file checkout.
594 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000595 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
596 # pylint: disable=E1103
597 options.revision = parsed_url.GetRevision()
598 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000599 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000600 self.name)
601 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000602 args + [parsed_url.GetFilename()],
603 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000604 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000605 # Create a shallow copy to mutate revision.
606 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000607 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000608 self.maybeGetParentRevision(
609 command, options, parsed_url, self.parent.name, revision_overrides)
610 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
611 scm.RunCommand(command, options, args, file_list)
612 self.maybeConvertToDateRevision(
613 command, options, self.name, scm, revision_overrides)
614 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000615
616 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
617 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000618 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000619 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000620 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000621 continue
622 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000623 [self.root.root_dir.lower(), file_list[i].lower()])
624 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000625 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000626 while file_list[i].startswith(('\\', '/')):
627 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000628
629 # Always parse the DEPS file.
630 self.ParseDepsFile()
631
632 self._run_is_done(file_list, parsed_url)
633
634 if self.recursion_limit:
635 # Parse the dependencies of this dependency.
636 for s in self.dependencies:
637 work_queue.enqueue(s)
638
639 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000640 if not isinstance(parsed_url, self.FileImpl):
641 # Skip file only checkout.
642 scm = gclient_scm.GetScmName(parsed_url)
643 if not options.scm or scm in options.scm:
644 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
645 # Pass in the SCM type as an env variable
646 env = os.environ.copy()
647 if scm:
648 env['GCLIENT_SCM'] = scm
649 if parsed_url:
650 env['GCLIENT_URL'] = parsed_url
ilevy@chromium.org37116242012-11-28 01:32:48 +0000651 env['GCLIENT_DEP_PATH'] = self.name
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000652 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000653 print_stdout = False
654 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000655 """Git-specific path marshaling. It is optimized for git-grep."""
656
657 def mod_path(git_pathspec):
658 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
659 modified_path = os.path.join(self.name, match.group(2))
660 branch = match.group(1) or ''
661 return '%s%s' % (branch, modified_path)
662
663 match = re.match('^Binary file ([^\0]+) matches$', line)
664 if match:
665 print 'Binary file %s matches' % mod_path(match.group(1))
666 return
667
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000668 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000669 if len(items) == 2 and items[1]:
670 print '%s : %s' % (mod_path(items[0]), items[1])
671 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000672 # Multiple null bytes or a single trailing null byte indicate
673 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000674 print '\n'.join(mod_path(path) for path in items if path)
675 else:
676 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000677 else:
678 print_stdout = True
679 filter_fn = None
680
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000681 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000682 try:
683 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000684 args, cwd=cwd, env=env, print_stdout=print_stdout,
685 filter_fn=filter_fn,
686 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000687 except subprocess2.CalledProcessError:
688 if not options.ignore:
689 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000690 else:
691 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000692
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000693
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000694 @gclient_utils.lockedmethod
695 def _run_is_done(self, file_list, parsed_url):
696 # Both these are kept for hooks that are run as a separate tree traversal.
697 self._file_list = file_list
698 self._parsed_url = parsed_url
699 self._processed = True
700
szager@google.comb9a78d32012-03-13 18:46:21 +0000701 @staticmethod
702 def GetHookAction(hook_dict, matching_file_list):
703 """Turns a parsed 'hook' dict into an executable command."""
704 logging.debug(hook_dict)
705 logging.debug(matching_file_list)
706 command = hook_dict['action'][:]
707 if command[0] == 'python':
708 # If the hook specified "python" as the first item, the action is a
709 # Python script. Run it by starting a new copy of the same
710 # interpreter.
711 command[0] = sys.executable
712 if '$matching_files' in command:
713 splice_index = command.index('$matching_files')
714 command[splice_index:splice_index + 1] = matching_file_list
715 return command
716
717 def GetHooks(self, options):
718 """Evaluates all hooks, and return them in a flat list.
719
720 RunOnDeps() must have been called before to load the DEPS.
721 """
722 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000723 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000724 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000725 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000726 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000727 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000728 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000729 # TODO(maruel): If the user is using git or git-svn, then we don't know
730 # what files have changed so we always run all hooks. It'd be nice to fix
731 # that.
732 if (options.force or
733 isinstance(self.parsed_url, self.FileImpl) or
734 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000735 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000736 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000737 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000738 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000739 # Run hooks on the basis of whether the files from the gclient operation
740 # match each hook's pattern.
741 for hook_dict in self.deps_hooks:
742 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000743 matching_file_list = [
744 f for f in self.file_list_and_children if pattern.search(f)
745 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000746 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000747 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000748 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000749 result.extend(s.GetHooks(options))
750 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000751
szager@google.comb9a78d32012-03-13 18:46:21 +0000752 def RunHooksRecursively(self, options):
753 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000754 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000755 for hook in self.GetHooks(options):
756 try:
757 gclient_utils.CheckCallAndFilterAndHeader(
758 hook, cwd=self.root.root_dir, always=True)
759 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
760 # Use a discrete exit status code of 2 to indicate that a hook action
761 # failed. Users of this script may wish to treat hook action failures
762 # differently from VC failures.
763 print >> sys.stderr, 'Error: %s' % str(e)
764 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000765
maruel@chromium.org0d812442010-08-10 12:41:08 +0000766 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000767 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000768 dependencies = self.dependencies
769 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000770 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000771 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000772 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000773 for i in d.subtree(include_all):
774 yield i
775
776 def depth_first_tree(self):
777 """Depth-first recursion including the root node."""
778 yield self
779 for i in self.dependencies:
780 for j in i.depth_first_tree():
781 if j.should_process:
782 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000783
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000784 @gclient_utils.lockedmethod
785 def add_dependency(self, new_dep):
786 self._dependencies.append(new_dep)
787
788 @gclient_utils.lockedmethod
789 def _mark_as_parsed(self, new_hooks):
790 self._deps_hooks.extend(new_hooks)
791 self._deps_parsed = True
792
maruel@chromium.org68988972011-09-20 14:11:42 +0000793 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000794 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000795 def dependencies(self):
796 return tuple(self._dependencies)
797
798 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000799 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000800 def deps_hooks(self):
801 return tuple(self._deps_hooks)
802
803 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000804 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000805 def parsed_url(self):
806 return self._parsed_url
807
808 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000809 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000810 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000811 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000812 return self._deps_parsed
813
814 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000815 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000816 def processed(self):
817 return self._processed
818
819 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000820 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000821 def hooks_ran(self):
822 return self._hooks_ran
823
824 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000825 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000826 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000827 return tuple(self._file_list)
828
829 @property
830 def file_list_and_children(self):
831 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000832 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000833 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000834 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000835
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000836 def __str__(self):
837 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000838 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000839 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000840 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000841 # First try the native property if it exists.
842 if hasattr(self, '_' + i):
843 value = getattr(self, '_' + i, False)
844 else:
845 value = getattr(self, i, False)
846 if value:
847 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000848
849 for d in self.dependencies:
850 out.extend([' ' + x for x in str(d).splitlines()])
851 out.append('')
852 return '\n'.join(out)
853
854 def __repr__(self):
855 return '%s: %s' % (self.name, self.url)
856
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000857 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000858 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000859 out = '%s(%s)' % (self.name, self.url)
860 i = self.parent
861 while i and i.name:
862 out = '%s(%s) -> %s' % (i.name, i.url, out)
863 i = i.parent
864 return out
865
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000866
867class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000868 """Object that represent a gclient checkout. A tree of Dependency(), one per
869 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000870
871 DEPS_OS_CHOICES = {
872 "win32": "win",
873 "win": "win",
874 "cygwin": "win",
875 "darwin": "mac",
876 "mac": "mac",
877 "unix": "unix",
878 "linux": "unix",
879 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000880 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000881 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000882 }
883
884 DEFAULT_CLIENT_FILE_TEXT = ("""\
885solutions = [
886 { "name" : "%(solution_name)s",
887 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000888 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000889 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000890 "custom_deps" : {
891 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000892 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000893 },
894]
895""")
896
897 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
898 { "name" : "%(solution_name)s",
899 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000900 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000901 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000902 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000903%(solution_deps)s },
904 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000905 },
906""")
907
908 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
909# Snapshot generated with gclient revinfo --snapshot
910solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000911%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000912""")
913
914 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000915 # Do not change previous behavior. Only solution level and immediate DEPS
916 # are processed.
917 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000918 Dependency.__init__(self, None, None, None, None, True, None, None,
919 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000920 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000921 if options.deps_os:
922 enforced_os = options.deps_os.split(',')
923 else:
924 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
925 if 'all' in enforced_os:
926 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000927 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000928 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000929 self.config_content = None
930
931 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000932 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000933 config_dict = {}
934 self.config_content = content
935 try:
936 exec(content, config_dict)
937 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000938 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000939
peter@chromium.org1efccc82012-04-27 16:34:38 +0000940 # Append any target OS that is not already being enforced to the tuple.
941 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000942 if config_dict.get('target_os_only', False):
943 self._enforced_os = tuple(set(target_os))
944 else:
945 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
946
947 if not target_os and config_dict.get('target_os_only', False):
948 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
949 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +0000950
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000951 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000952 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000953 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000954 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000955 self, s['name'], s['url'],
956 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000957 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000958 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000959 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000960 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000961 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000962 except KeyError:
963 raise gclient_utils.Error('Invalid .gclient file. Solution is '
964 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000965 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
966 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000967
968 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000969 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000970 self._options.config_filename),
971 self.config_content)
972
973 @staticmethod
974 def LoadCurrentConfig(options):
975 """Searches for and loads a .gclient file relative to the current working
976 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +0000977 if options.spec:
978 client = GClient('.', options)
979 client.SetConfig(options.spec)
980 else:
981 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
982 if not path:
983 return None
984 client = GClient(path, options)
985 client.SetConfig(gclient_utils.FileRead(
986 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000987
988 if (options.revisions and
989 len(client.dependencies) > 1 and
990 any('@' not in r for r in options.revisions)):
991 print >> sys.stderr, (
992 'You must specify the full solution name like --revision %s@%s\n'
993 'when you have multiple solutions setup in your .gclient file.\n'
994 'Other solutions present are: %s.') % (
995 client.dependencies[0].name,
996 options.revisions[0],
997 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000998 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000999
nsylvain@google.comefc80932011-05-31 21:27:56 +00001000 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001001 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001002 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1003 'solution_name': solution_name,
1004 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001005 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001006 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001007 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001008 })
1009
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001010 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001011 """Creates a .gclient_entries file to record the list of unique checkouts.
1012
1013 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001014 """
1015 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1016 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001017 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001018 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001019 # Skip over File() dependencies as we can't version them.
1020 if not isinstance(entry.parsed_url, self.FileImpl):
1021 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1022 pprint.pformat(entry.parsed_url))
1023 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001024 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001025 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001026 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001027
1028 def _ReadEntries(self):
1029 """Read the .gclient_entries file for the given client.
1030
1031 Returns:
1032 A sequence of solution names, which will be empty if there is the
1033 entries file hasn't been created yet.
1034 """
1035 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001036 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001037 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001038 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001039 try:
1040 exec(gclient_utils.FileRead(filename), scope)
1041 except SyntaxError, e:
1042 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001043 return scope['entries']
1044
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001045 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001046 """Checks for revision overrides."""
1047 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001048 if self._options.head:
1049 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001050 # Do not check safesync_url if one or more --revision flag is specified.
1051 if not self._options.revisions:
1052 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001053 if not s.managed:
1054 self._options.revisions.append('%s@unmanaged' % s.name)
1055 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001056 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001057 if not self._options.revisions:
1058 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001059 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001060 index = 0
1061 for revision in self._options.revisions:
1062 if not '@' in revision:
1063 # Support for --revision 123
1064 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001065 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001066 if not sol in solutions_names:
1067 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1068 print >> sys.stderr, ('Please fix your script, having invalid '
1069 '--revision flags will soon considered an error.')
1070 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001071 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001072 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001073 return revision_overrides
1074
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001075 def _ApplySafeSyncRev(self, dep):
1076 """Finds a valid revision from the content of the safesync_url and apply it
1077 by appending revisions to the revision list. Throws if revision appears to
1078 be invalid for the given |dep|."""
1079 assert len(dep.safesync_url) > 0
1080 handle = urllib.urlopen(dep.safesync_url)
1081 rev = handle.read().strip()
1082 handle.close()
1083 if not rev:
1084 raise gclient_utils.Error(
1085 'It appears your safesync_url (%s) is not working properly\n'
1086 '(as it returned an empty response). Check your config.' %
1087 dep.safesync_url)
1088 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
1089 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
1090 if self._options.verbose:
1091 print('Using safesync_url revision: %s.\n' % safe_rev)
1092 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1093
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001094 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001095 """Runs a command on each dependency in a client and its dependencies.
1096
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001097 Args:
1098 command: The command to use (e.g., 'status' or 'diff')
1099 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001100 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001101 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001102 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001103 revision_overrides = {}
1104 # It's unnecessary to check for revision overrides for 'recurse'.
1105 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001106 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001107 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001108 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001109 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001110 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001111 if command in ('update', 'revert'):
1112 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001113 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001114 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001115 work_queue = gclient_utils.ExecutionQueue(
1116 self._options.jobs, pm, ignore_requirements=ignore_requirements)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001117 for s in self.dependencies:
1118 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001119 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001120
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001121 # Once all the dependencies have been processed, it's now safe to run the
1122 # hooks.
1123 if not self._options.nohooks:
1124 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001125
1126 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001127 # Notify the user if there is an orphaned entry in their working copy.
1128 # Only delete the directory if there are no changes in it, and
1129 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001130 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001131 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001132 if not prev_url:
1133 # entry must have been overridden via .gclient custom_deps
1134 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001135 # Fix path separator on Windows.
1136 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001137 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001138
1139 def _IsParentOfAny(parent, path_list):
1140 parent_plus_slash = parent + '/'
1141 return any(
1142 path[:len(parent_plus_slash)] == parent_plus_slash
1143 for path in path_list)
1144
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001145 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001146 if (entry not in entries and
1147 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001148 os.path.exists(e_dir)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001149 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001150 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001151 scm.status(self._options, [], file_list)
1152 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001153 if (not self._options.delete_unversioned_trees or
1154 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001155 # There are modified files in this entry. Keep warning until
1156 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001157 print(('\nWARNING: \'%s\' is no longer part of this client. '
1158 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001159 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001160 else:
1161 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001162 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001163 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001164 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001165 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001166 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001167 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001168
1169 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001170 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001171 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001172 # Load all the settings.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001173 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None, False)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001174 for s in self.dependencies:
1175 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001176 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001177
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001178 def GetURLAndRev(dep):
1179 """Returns the revision-qualified SCM url for a Dependency."""
1180 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001181 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001182 if isinstance(dep.parsed_url, self.FileImpl):
1183 original_url = dep.parsed_url.file_location
1184 else:
1185 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001186 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001187 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001188 if not os.path.isdir(scm.checkout_path):
1189 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001190 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001191
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001192 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001193 new_gclient = ''
1194 # First level at .gclient
1195 for d in self.dependencies:
1196 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001197 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001198 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001199 for d in dep.dependencies:
1200 entries[d.name] = GetURLAndRev(d)
1201 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001202 GrabDeps(d)
1203 custom_deps = []
1204 for k in sorted(entries.keys()):
1205 if entries[k]:
1206 # Quotes aren't escaped...
1207 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1208 else:
1209 custom_deps.append(' \"%s\": None,\n' % k)
1210 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1211 'solution_name': d.name,
1212 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001213 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001214 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001215 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001216 'solution_deps': ''.join(custom_deps),
1217 }
1218 # Print the snapshot configuration file
1219 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001220 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001221 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001222 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001223 if self._options.actual:
1224 entries[d.name] = GetURLAndRev(d)
1225 else:
1226 entries[d.name] = d.parsed_url
1227 keys = sorted(entries.keys())
1228 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001229 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001230 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001231
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001232 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001233 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001234 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001235
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001236 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001237 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001238 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001239 return self._root_dir
1240
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001241 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001242 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001243 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001244 return self._enforced_os
1245
maruel@chromium.org68988972011-09-20 14:11:42 +00001246 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001247 def recursion_limit(self):
1248 """How recursive can each dependencies in DEPS file can load DEPS file."""
1249 return self._recursion_limit
1250
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001251 @property
1252 def target_os(self):
1253 return self._enforced_os
1254
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001255
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001256#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001257
1258
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001259def CMDcleanup(parser, args):
1260 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001261
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001262Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001263"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001264 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1265 help='override deps for the specified (comma-separated) '
1266 'platform(s); \'all\' will process all deps_os '
1267 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001268 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001269 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001270 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001271 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001272 if options.verbose:
1273 # Print out the .gclient file. This is longer than if we just printed the
1274 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001275 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001276 return client.RunOnDeps('cleanup', args)
1277
1278
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001279@attr('usage', '[command] [args ...]')
1280def CMDrecurse(parser, args):
1281 """Operates on all the entries.
1282
1283 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001284 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1285 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001286 """
1287 # Stop parsing at the first non-arg so that these go through to the command
1288 parser.disable_interspersed_args()
1289 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001290 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001291 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001292 help='Ignore non-zero return codes from subcommands.')
1293 parser.add_option('--prepend-dir', action='store_true',
1294 help='Prepend relative dir for use with git <cmd> --null.')
1295 parser.add_option('--no-progress', action='store_true',
1296 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001297 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001298 if not args:
1299 print >> sys.stderr, 'Need to supply a command!'
1300 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001301 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1302 if not root_and_entries:
1303 print >> sys.stderr, (
1304 'You need to run gclient sync at least once to use \'recurse\'.\n'
1305 'This is because .gclient_entries needs to exist and be up to date.')
1306 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001307
1308 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001309 scm_set = set()
1310 for scm in options.scm:
1311 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001312 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001313
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001314 options.nohooks = True
1315 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001316 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1317 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001318
1319
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001320@attr('usage', '[args ...]')
1321def CMDfetch(parser, args):
1322 """Fetches upstream commits for all modules.
1323
1324Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1325"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001326 (options, args) = parser.parse_args(args)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001327 return CMDrecurse(Parser(), [
1328 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1329
1330
1331def CMDgrep(parser, args):
1332 """Greps through git repos managed by gclient.
1333
1334Runs 'git grep [args...]' for each module.
1335"""
1336
1337 # We can't use optparse because it will try to parse arguments sent
1338 # to git grep and throw an error. :-(
1339 if not args or re.match('(-h|--help)$', args[0]):
1340 print >> sys.stderr, (
1341 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1342 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1343 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1344 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1345 ' end of your query.'
1346 )
1347 return 1
1348
1349 jobs_arg = ['--jobs=1']
1350 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1351 jobs_arg, args = args[:1], args[1:]
1352 elif re.match(r'(-j|--jobs)$', args[0]):
1353 jobs_arg, args = args[:2], args[2:]
1354
1355 return CMDrecurse(
1356 parser,
1357 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1358 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001359
1360
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001361@attr('usage', '[url] [safesync url]')
1362def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001363 """Create a .gclient file in the current directory.
1364
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001365This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001366top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001367modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001368provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001369URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001370"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001371
1372 # We do a little dance with the --gclientfile option. 'gclient config' is the
1373 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1374 # arguments. So, we temporarily stash any --gclientfile parameter into
1375 # options.output_config_file until after the (gclientfile xor spec) error
1376 # check.
1377 parser.remove_option('--gclientfile')
1378 parser.add_option('--gclientfile', dest='output_config_file',
1379 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001380 parser.add_option('--name',
1381 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001382 parser.add_option('--deps-file', default='DEPS',
1383 help='overrides the default name for the DEPS file for the'
1384 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001385 parser.add_option('--unmanaged', action='store_true', default=False,
1386 help='overrides the default behavior to make it possible '
1387 'to have the main solution untouched by gclient '
1388 '(gclient will check out unmanaged dependencies but '
1389 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001390 parser.add_option('--git-deps', action='store_true',
1391 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001392 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001393 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001394 if options.output_config_file:
1395 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001396 if ((options.spec and args) or len(args) > 2 or
1397 (not options.spec and not args)):
1398 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1399
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001400 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001401 if options.spec:
1402 client.SetConfig(options.spec)
1403 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001404 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001405 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001406 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001407 if name.endswith('.git'):
1408 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001409 else:
1410 # specify an alternate relpath for the given URL.
1411 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001412 deps_file = options.deps_file
1413 if options.git_deps:
1414 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001415 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001416 if len(args) > 1:
1417 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001418 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1419 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001420 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001421 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001422
1423
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001424@attr('epilog', """Example:
1425 gclient pack > patch.txt
1426 generate simple patch for configured client and dependences
1427""")
1428def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001429 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001430
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001431Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001432dependencies, and performs minimal postprocessing of the output. The
1433resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001434checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001435"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001436 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1437 help='override deps for the specified (comma-separated) '
1438 'platform(s); \'all\' will process all deps_os '
1439 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001440 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001441 (options, args) = parser.parse_args(args)
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001442 # Force jobs to 1 so the stdout is not annotated with the thread ids
1443 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001444 client = GClient.LoadCurrentConfig(options)
1445 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001446 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001447 if options.verbose:
1448 # Print out the .gclient file. This is longer than if we just printed the
1449 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001450 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001451 return client.RunOnDeps('pack', args)
1452
1453
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001454def CMDstatus(parser, args):
1455 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001456 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1457 help='override deps for the specified (comma-separated) '
1458 'platform(s); \'all\' will process all deps_os '
1459 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001460 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001461 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001462 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001463 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001464 if options.verbose:
1465 # Print out the .gclient file. This is longer than if we just printed the
1466 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001467 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001468 return client.RunOnDeps('status', args)
1469
1470
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001471@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001472 gclient sync
1473 update files from SCM according to current configuration,
1474 *for modules which have changed since last update or sync*
1475 gclient sync --force
1476 update files from SCM according to current configuration, for
1477 all modules (useful for recovering files deleted from local copy)
1478 gclient sync --revision src@31000
1479 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001480""")
1481def CMDsync(parser, args):
1482 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001483 parser.add_option('-f', '--force', action='store_true',
1484 help='force update even for unchanged modules')
1485 parser.add_option('-n', '--nohooks', action='store_true',
1486 help='don\'t run hooks after the update is complete')
1487 parser.add_option('-r', '--revision', action='append',
1488 dest='revisions', metavar='REV', default=[],
1489 help='Enforces revision/hash for the solutions with the '
1490 'format src@rev. The src@ part is optional and can be '
1491 'skipped. -r can be used multiple times when .gclient '
1492 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001493 'if the src@ part is skipped. Note that specifying '
1494 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001495 parser.add_option('--with_branch_heads', action='store_true',
1496 help='Clone git "branch_heads" refspecs in addition to '
1497 'the default refspecs. This adds about 1/2GB to a '
1498 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001499 parser.add_option('-t', '--transitive', action='store_true',
1500 help='When a revision is specified (in the DEPS file or '
1501 'with the command-line flag), transitively update '
1502 'the dependencies to the date of the given revision. '
1503 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001504 parser.add_option('-H', '--head', action='store_true',
1505 help='skips any safesync_urls specified in '
1506 'configured solutions and sync to head instead')
1507 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001508 help='Deletes from the working copy any dependencies that '
1509 'have been removed since the last sync, as long as '
1510 'there are no local modifications. When used with '
1511 '--force, such dependencies are removed even if they '
1512 'have local modifications. When used with --reset, '
1513 'all untracked directories are removed from the '
1514 'working copy, exclusing those which are explicitly '
1515 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001516 parser.add_option('-R', '--reset', action='store_true',
1517 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001518 parser.add_option('-M', '--merge', action='store_true',
1519 help='merge upstream changes instead of trying to '
1520 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001521 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1522 help='override deps for the specified (comma-separated) '
1523 'platform(s); \'all\' will process all deps_os '
1524 'references')
1525 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1526 help='Skip svn up whenever possible by requesting '
1527 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001528 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001529 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001530
1531 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001532 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001533
maruel@chromium.org307d1792010-05-31 20:03:13 +00001534 if options.revisions and options.head:
1535 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001536 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001537
1538 if options.verbose:
1539 # Print out the .gclient file. This is longer than if we just printed the
1540 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001541 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001542 return client.RunOnDeps('update', args)
1543
1544
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001545def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001546 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001547 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001548
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001549def CMDdiff(parser, args):
1550 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001551 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1552 help='override deps for the specified (comma-separated) '
1553 'platform(s); \'all\' will process all deps_os '
1554 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001555 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001556 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001557 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001558 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001559 if options.verbose:
1560 # Print out the .gclient file. This is longer than if we just printed the
1561 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001562 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001563 return client.RunOnDeps('diff', args)
1564
1565
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001566def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001567 """Revert all modifications in every dependencies.
1568
1569 That's the nuclear option to get back to a 'clean' state. It removes anything
1570 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001571 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1572 help='override deps for the specified (comma-separated) '
1573 'platform(s); \'all\' will process all deps_os '
1574 'references')
1575 parser.add_option('-n', '--nohooks', action='store_true',
1576 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001577 (options, args) = parser.parse_args(args)
1578 # --force is implied.
1579 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001580 options.reset = False
1581 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001582 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001583 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001584 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001585 return client.RunOnDeps('revert', args)
1586
1587
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001588def CMDrunhooks(parser, args):
1589 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001590 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1591 help='override deps for the specified (comma-separated) '
1592 'platform(s); \'all\' will process all deps_os '
1593 'references')
1594 parser.add_option('-f', '--force', action='store_true', default=True,
1595 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001596 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001597 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001598 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001599 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001600 if options.verbose:
1601 # Print out the .gclient file. This is longer than if we just printed the
1602 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001603 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001604 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001605 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001606 return client.RunOnDeps('runhooks', args)
1607
1608
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001609def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001610 """Output revision info mapping for the client and its dependencies.
1611
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001612 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001613 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001614 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1615 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001616 commit can change.
1617 """
1618 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1619 help='override deps for the specified (comma-separated) '
1620 'platform(s); \'all\' will process all deps_os '
1621 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001622 parser.add_option('-a', '--actual', action='store_true',
1623 help='gets the actual checked out revisions instead of the '
1624 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001625 parser.add_option('-s', '--snapshot', action='store_true',
1626 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001627 'version of all repositories to reproduce the tree, '
1628 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001629 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001630 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001631 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001632 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001633 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001634 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001635
1636
szager@google.comb9a78d32012-03-13 18:46:21 +00001637def CMDhookinfo(parser, args):
1638 """Output the hooks that would be run by `gclient runhooks`"""
1639 (options, args) = parser.parse_args(args)
1640 options.force = True
1641 client = GClient.LoadCurrentConfig(options)
1642 if not client:
1643 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1644 client.RunOnDeps(None, [])
1645 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1646 return 0
1647
1648
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001649def Command(name):
1650 return getattr(sys.modules[__name__], 'CMD' + name, None)
1651
1652
1653def CMDhelp(parser, args):
1654 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001655 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001656 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001657 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001658 parser.print_help()
1659 return 0
1660
1661
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001662def GenUsage(parser, command):
1663 """Modify an OptParse object with the function's documentation."""
1664 obj = Command(command)
1665 if command == 'help':
1666 command = '<command>'
1667 # OptParser.description prefer nicely non-formatted strings.
1668 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1669 usage = getattr(obj, 'usage', '')
1670 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1671 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001672
1673
maruel@chromium.org0895b752011-08-26 20:40:33 +00001674def Parser():
1675 """Returns the default parser."""
1676 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001677 # some arm boards have issues with parallel sync.
1678 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001679 jobs = 1
1680 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001681 jobs = max(8, gclient_utils.NumLocalCpus())
szager@chromium.orge2e03202012-07-31 18:05:16 +00001682 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001683 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001684 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001685 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001686 parser.add_option('-v', '--verbose', action='count', default=0,
1687 help='Produces additional output for diagnostics. Can be '
1688 'used up to three times for more logging info.')
1689 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001690 default=None,
1691 help='Specify an alternate %s file' % gclientfile_default)
1692 parser.add_option('--spec',
1693 default=None,
1694 help='create a gclient file containing the provided '
1695 'string. Due to Cygwin/Python brokenness, it '
1696 'probably can\'t contain any newlines.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001697 # Integrate standard options processing.
1698 old_parser = parser.parse_args
1699 def Parse(args):
1700 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001701 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1702 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001703 logging.basicConfig(level=level,
1704 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001705 if options.config_filename and options.spec:
1706 parser.error('Cannot specifiy both --gclientfile and --spec')
1707 if not options.config_filename:
1708 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001709 options.entries_filename = options.config_filename + '_entries'
1710 if options.jobs < 1:
1711 parser.error('--jobs must be 1 or higher')
1712
1713 # These hacks need to die.
1714 if not hasattr(options, 'revisions'):
1715 # GClient.RunOnDeps expects it even if not applicable.
1716 options.revisions = []
1717 if not hasattr(options, 'head'):
1718 options.head = None
1719 if not hasattr(options, 'nohooks'):
1720 options.nohooks = True
1721 if not hasattr(options, 'deps_os'):
1722 options.deps_os = None
1723 if not hasattr(options, 'manually_grab_svn_rev'):
1724 options.manually_grab_svn_rev = None
1725 if not hasattr(options, 'force'):
1726 options.force = None
1727 return (options, args)
1728 parser.parse_args = Parse
1729 # We don't want wordwrapping in epilog (usually examples)
1730 parser.format_epilog = lambda _: parser.epilog or ''
1731 return parser
1732
1733
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001734def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001735 """Doesn't parse the arguments here, just find the right subcommand to
1736 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001737 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001738 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001739 '\nYour python version %s is unsupported, please upgrade.\n' %
1740 sys.version.split(' ', 1)[0])
1741 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001742 if not sys.executable:
1743 print >> sys.stderr, (
1744 '\nPython cannot find the location of it\'s own executable.\n')
1745 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001746 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001747 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001748 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1749 # operations. Python as a strong tendency to buffer sys.stdout.
1750 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001751 # Make stdout annotated with the thread ids.
1752 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001753 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001754 # Unused variable 'usage'
1755 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001756 def to_str(fn):
1757 return (
1758 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1759 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1760 cmds = (
1761 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1762 )
1763 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001764 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001765 if argv:
1766 command = Command(argv[0])
1767 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001768 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001769 GenUsage(parser, argv[0])
1770 return command(parser, argv[1:])
1771 # Not a known command. Default to help.
1772 GenUsage(parser, 'help')
1773 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001774 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001775 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001776 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001777
1778
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001779if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001780 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001781 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001782
1783# vim: ts=2:sw=2:tw=80:et: