blob: 8c54d374320d2251dda917397362818b1fba5d62 [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
maruel@chromium.org064186c2011-09-27 23:53:33 +0000190 # Post process the url to remove trailing slashes.
191 if isinstance(self._url, basestring):
192 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
193 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000194 self._url = self._url.replace('/@', '@')
195 elif not isinstance(self._url,
196 (self.FromImpl, self.FileImpl, None.__class__)):
197 raise gclient_utils.Error(
198 ('dependency url must be either a string, None, '
199 'File() or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000200 # Make any deps_file path platform-appropriate.
201 for sep in ['/', '\\']:
202 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000203
204 @property
205 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000206 return self._deps_file
207
208 @property
209 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000210 return self._managed
211
212 @property
213 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 return self._parent
215
216 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000217 def root(self):
218 """Returns the root node, a GClient object."""
219 if not self.parent:
220 # This line is to signal pylint that it could be a GClient instance.
221 return self or GClient(None, None)
222 return self.parent.root
223
224 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000226 return self._safesync_url
227
228 @property
229 def should_process(self):
230 """True if this dependency should be processed, i.e. checked out."""
231 return self._should_process
232
233 @property
234 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000235 return self._custom_vars.copy()
236
237 @property
238 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 return self._custom_deps.copy()
240
maruel@chromium.org064186c2011-09-27 23:53:33 +0000241 @property
242 def url(self):
243 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000245 @property
246 def recursion_limit(self):
247 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000248 if self.recursion_override is not None:
249 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000250 return max(self.parent.recursion_limit - 1, 0)
251
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000252 @property
253 def target_os(self):
254 if self.local_target_os is not None:
255 return tuple(set(self.local_target_os).union(self.parent.target_os))
256 else:
257 return self.parent.target_os
258
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000259 def get_custom_deps(self, name, url):
260 """Returns a custom deps if applicable."""
261 if self.parent:
262 url = self.parent.get_custom_deps(name, url)
263 # None is a valid return value to disable a dependency.
264 return self.custom_deps.get(name, url)
265
maruel@chromium.org064186c2011-09-27 23:53:33 +0000266
267class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000268 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000269
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000270 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000271 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000272 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000273 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000275 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000276
277 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000278 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000279
280 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000281 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000282 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000283 # A cache of the files affected by the current operation, necessary for
284 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000285 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000286 # If it is not set to True, the dependency wasn't processed for its child
287 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000288 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000289 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000290 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000291 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000292 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000293
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000294 if not self.name and self.parent:
295 raise gclient_utils.Error('Dependency without name')
296
maruel@chromium.org470b5432011-10-11 18:18:19 +0000297 @property
298 def requirements(self):
299 """Calculate the list of requirements."""
300 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000301 # self.parent is implicitly a requirement. This will be recursive by
302 # definition.
303 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000304 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000305
306 # For a tree with at least 2 levels*, the leaf node needs to depend
307 # on the level higher up in an orderly way.
308 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
309 # thus unsorted, while the .gclient format is a list thus sorted.
310 #
311 # * _recursion_limit is hard coded 2 and there is no hope to change this
312 # value.
313 #
314 # Interestingly enough, the following condition only works in the case we
315 # want: self is a 2nd level node. 3nd level node wouldn't need this since
316 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000317 if self.parent and self.parent.parent and not self.parent.parent.parent:
318 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000319
320 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000321 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000322
maruel@chromium.org470b5432011-10-11 18:18:19 +0000323 if self.name:
324 requirements |= set(
325 obj.name for obj in self.root.subtree(False)
326 if (obj is not self
327 and obj.name and
328 self.name.startswith(posixpath.join(obj.name, ''))))
329 requirements = tuple(sorted(requirements))
330 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
331 return requirements
332
333 def verify_validity(self):
334 """Verifies that this Dependency is fine to add as a child of another one.
335
336 Returns True if this entry should be added, False if it is a duplicate of
337 another entry.
338 """
339 logging.info('Dependency(%s).verify_validity()' % self.name)
340 if self.name in [s.name for s in self.parent.dependencies]:
341 raise gclient_utils.Error(
342 'The same name "%s" appears multiple times in the deps section' %
343 self.name)
344 if not self.should_process:
345 # Return early, no need to set requirements.
346 return True
347
348 # This require a full tree traversal with locks.
349 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
350 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000351 self_url = self.LateOverride(self.url)
352 sibling_url = sibling.LateOverride(sibling.url)
353 # Allow to have only one to be None or ''.
354 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000355 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000356 ('Dependency %s specified more than once:\n'
357 ' %s [%s]\n'
358 'vs\n'
359 ' %s [%s]') % (
360 self.name,
361 sibling.hierarchy(),
362 sibling_url,
363 self.hierarchy(),
364 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000365 # In theory we could keep it as a shadow of the other one. In
366 # practice, simply ignore it.
367 logging.warn('Won\'t process duplicate dependency %s' % sibling)
368 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000369 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000370
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000371 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000372 """Resolves the parsed url from url.
373
374 Manages From() keyword accordingly. Do not touch self.parsed_url nor
375 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000376 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000377 parsed_url = self.get_custom_deps(self.name, url)
378 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000379 logging.info(
380 'Dependency(%s).LateOverride(%s) -> %s' %
381 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000382 return parsed_url
383
384 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000385 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000386 ref = [
387 dep for dep in self.root.subtree(True) if url.module_name == dep.name
388 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000389 if not ref:
390 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
391 url.module_name, ref))
392 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000393 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000394 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000395 found_deps = [d for d in ref.dependencies if d.name == sub_target]
396 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000397 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000398 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
399 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000400 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000401
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000402 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000403 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000404 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000405 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000406 'Dependency(%s).LateOverride(%s) -> %s (From)' %
407 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000408 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000409
410 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000411 parsed_url = urlparse.urlparse(url)
412 if not parsed_url[0]:
413 # A relative url. Fetch the real base.
414 path = parsed_url[2]
415 if not path.startswith('/'):
416 raise gclient_utils.Error(
417 'relative DEPS entry \'%s\' must begin with a slash' % url)
418 # Create a scm just to query the full url.
419 parent_url = self.parent.parsed_url
420 if isinstance(parent_url, self.FileImpl):
421 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000422 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000423 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000424 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000425 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000426 logging.info(
427 'Dependency(%s).LateOverride(%s) -> %s' %
428 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000429 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000430
431 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000432 logging.info(
433 'Dependency(%s).LateOverride(%s) -> %s (File)' %
434 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000435 return url
436
437 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000438 logging.info(
439 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000440 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000441
442 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000443
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000444 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000445 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000446 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000447 assert not self.dependencies
448 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000449 local_scope = {}
450 var = self.VarImpl(self.custom_vars, local_scope)
451 global_scope = {
452 'File': self.FileImpl,
453 'From': self.FromImpl,
454 'Var': var.Lookup,
455 'deps_os': {},
456 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000457 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000458 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000459 logging.info(
460 'ParseDepsFile(%s): No %s file found at %s' % (
461 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000462 else:
463 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000464 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000465 # Eval the content.
466 try:
467 exec(deps_content, global_scope, local_scope)
468 except SyntaxError, e:
469 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000470 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000471 if 'recursion' in local_scope:
472 self.recursion_override = local_scope.get('recursion')
473 logging.warning(
474 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000475 # If present, save 'target_os' in the local_target_os property.
476 if 'target_os' in local_scope:
477 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000478 # load os specific dependencies if defined. these dependencies may
479 # override or extend the values defined by the 'deps' member.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000480 target_os_deps = {}
maruel@chromium.org271375b2010-06-23 19:17:38 +0000481 if 'deps_os' in local_scope:
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000482 for deps_os_key in self.target_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000483 os_deps = local_scope['deps_os'].get(deps_os_key, {})
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000484 if len(self.target_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000485 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000486 # platform, so we collect the broadest set of dependencies
487 # available. We may end up with the wrong revision of something for
488 # our platform, but this is the best we can do.
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000489 target_os_deps.update(
490 [x for x in os_deps.items() if not x[0] in target_os_deps])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000491 else:
sivachandra@chromium.orga0ad8ad2012-11-06 19:41:28 +0000492 target_os_deps.update(os_deps)
493
494 # deps_os overrides paths from deps
495 deps.update(target_os_deps)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000496
maruel@chromium.org271375b2010-06-23 19:17:38 +0000497 # If a line is in custom_deps, but not in the solution, we want to append
498 # this line to the solution.
499 for d in self.custom_deps:
500 if d not in deps:
501 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000502
503 # If use_relative_paths is set in the DEPS file, regenerate
504 # the dictionary using paths relative to the directory containing
505 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000506 use_relative_paths = local_scope.get('use_relative_paths', False)
507 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000508 rel_deps = {}
509 for d, url in deps.items():
510 # normpath is required to allow DEPS to use .. in their
511 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000512 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
513 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000514
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000515 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000516 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000517 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000518 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000519 deps_to_add.append(Dependency(
520 self, name, url, None, None, None, None,
521 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000522 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000523 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
524 logging.info('ParseDepsFile(%s) done' % self.name)
525
526 def add_dependencies_and_close(self, deps_to_add, hooks):
527 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000528 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000529 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000530 self.add_dependency(dep)
531 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000532
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000533 @staticmethod
534 def maybeGetParentRevision(
535 command, options, parsed_url, parent_name, revision_overrides):
536 """If we are performing an update and --transitive is set, set the
537 revision to the parent's revision. If we have an explicit revision
538 do nothing."""
539 if command == 'update' and options.transitive and not options.revision:
540 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
541 if not revision:
542 options.revision = revision_overrides.get(parent_name)
543 if options.verbose and options.revision:
544 print("Using parent's revision date: %s" % options.revision)
545 # If the parent has a revision override, then it must have been
546 # converted to date format.
547 assert (not options.revision or
548 gclient_utils.IsDateRevision(options.revision))
549
550 @staticmethod
551 def maybeConvertToDateRevision(
552 command, options, name, scm, revision_overrides):
553 """If we are performing an update and --transitive is set, convert the
554 revision to a date-revision (if necessary). Instead of having
555 -r 101 replace the revision with the time stamp of 101 (e.g.
556 "{2011-18-04}").
557 This way dependencies are upgraded to the revision they had at the
558 check-in of revision 101."""
559 if (command == 'update' and
560 options.transitive and
561 options.revision and
562 not gclient_utils.IsDateRevision(options.revision)):
563 revision_date = scm.GetRevisionDate(options.revision)
564 revision = gclient_utils.MakeDateRevision(revision_date)
565 if options.verbose:
566 print("Updating revision override from %s to %s." %
567 (options.revision, revision))
568 revision_overrides[name] = revision
569
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000570 # Arguments number differs from overridden method
571 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000572 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000573 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000574 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000575 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000576 if not self.should_process:
577 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000578 # When running runhooks, there's no need to consult the SCM.
579 # All known hooks are expected to run unconditionally regardless of working
580 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000581 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000582 parsed_url = self.LateOverride(self.url)
583 file_list = []
584 if run_scm and parsed_url:
585 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000586 # Special support for single-file checkout.
587 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000588 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
589 # pylint: disable=E1103
590 options.revision = parsed_url.GetRevision()
591 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000592 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000593 self.name)
594 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000595 args + [parsed_url.GetFilename()],
596 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000597 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000598 # Create a shallow copy to mutate revision.
599 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000600 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000601 self.maybeGetParentRevision(
602 command, options, parsed_url, self.parent.name, revision_overrides)
603 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
604 scm.RunCommand(command, options, args, file_list)
605 self.maybeConvertToDateRevision(
606 command, options, self.name, scm, revision_overrides)
607 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000608
609 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
610 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000611 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000612 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000613 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000614 continue
615 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000616 [self.root.root_dir.lower(), file_list[i].lower()])
617 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000618 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000619 while file_list[i].startswith(('\\', '/')):
620 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000621
622 # Always parse the DEPS file.
623 self.ParseDepsFile()
624
625 self._run_is_done(file_list, parsed_url)
626
627 if self.recursion_limit:
628 # Parse the dependencies of this dependency.
629 for s in self.dependencies:
630 work_queue.enqueue(s)
631
632 if command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000633 if not isinstance(parsed_url, self.FileImpl):
634 # Skip file only checkout.
635 scm = gclient_scm.GetScmName(parsed_url)
636 if not options.scm or scm in options.scm:
637 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
638 # Pass in the SCM type as an env variable
639 env = os.environ.copy()
640 if scm:
641 env['GCLIENT_SCM'] = scm
642 if parsed_url:
643 env['GCLIENT_URL'] = parsed_url
ilevy@chromium.org37116242012-11-28 01:32:48 +0000644 env['GCLIENT_DEP_PATH'] = self.name
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000645 if options.prepend_dir and scm == 'git':
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000646 print_stdout = False
647 def filter_fn(line):
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000648 """Git-specific path marshaling. It is optimized for git-grep."""
649
650 def mod_path(git_pathspec):
651 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
652 modified_path = os.path.join(self.name, match.group(2))
653 branch = match.group(1) or ''
654 return '%s%s' % (branch, modified_path)
655
656 match = re.match('^Binary file ([^\0]+) matches$', line)
657 if match:
658 print 'Binary file %s matches' % mod_path(match.group(1))
659 return
660
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000661 items = line.split('\0')
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000662 if len(items) == 2 and items[1]:
663 print '%s : %s' % (mod_path(items[0]), items[1])
664 elif len(items) >= 2:
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000665 # Multiple null bytes or a single trailing null byte indicate
666 # git is likely displaying filenames only (such as with -l)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000667 print '\n'.join(mod_path(path) for path in items if path)
668 else:
669 print line
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000670 else:
671 print_stdout = True
672 filter_fn = None
673
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000674 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000675 try:
676 gclient_utils.CheckCallAndFilter(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000677 args, cwd=cwd, env=env, print_stdout=print_stdout,
678 filter_fn=filter_fn,
679 )
maruel@chromium.org288054d2012-03-05 00:43:07 +0000680 except subprocess2.CalledProcessError:
681 if not options.ignore:
682 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000683 else:
684 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000685
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000686
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000687 @gclient_utils.lockedmethod
688 def _run_is_done(self, file_list, parsed_url):
689 # Both these are kept for hooks that are run as a separate tree traversal.
690 self._file_list = file_list
691 self._parsed_url = parsed_url
692 self._processed = True
693
szager@google.comb9a78d32012-03-13 18:46:21 +0000694 @staticmethod
695 def GetHookAction(hook_dict, matching_file_list):
696 """Turns a parsed 'hook' dict into an executable command."""
697 logging.debug(hook_dict)
698 logging.debug(matching_file_list)
699 command = hook_dict['action'][:]
700 if command[0] == 'python':
701 # If the hook specified "python" as the first item, the action is a
702 # Python script. Run it by starting a new copy of the same
703 # interpreter.
704 command[0] = sys.executable
705 if '$matching_files' in command:
706 splice_index = command.index('$matching_files')
707 command[splice_index:splice_index + 1] = matching_file_list
708 return command
709
710 def GetHooks(self, options):
711 """Evaluates all hooks, and return them in a flat list.
712
713 RunOnDeps() must have been called before to load the DEPS.
714 """
715 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000716 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000717 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000718 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000719 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000720 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000721 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000722 # TODO(maruel): If the user is using git or git-svn, then we don't know
723 # what files have changed so we always run all hooks. It'd be nice to fix
724 # that.
725 if (options.force or
726 isinstance(self.parsed_url, self.FileImpl) or
727 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000728 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000729 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000730 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000731 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000732 # Run hooks on the basis of whether the files from the gclient operation
733 # match each hook's pattern.
734 for hook_dict in self.deps_hooks:
735 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000736 matching_file_list = [
737 f for f in self.file_list_and_children if pattern.search(f)
738 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000739 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000740 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000741 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000742 result.extend(s.GetHooks(options))
743 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000744
szager@google.comb9a78d32012-03-13 18:46:21 +0000745 def RunHooksRecursively(self, options):
746 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000747 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000748 for hook in self.GetHooks(options):
749 try:
750 gclient_utils.CheckCallAndFilterAndHeader(
751 hook, cwd=self.root.root_dir, always=True)
752 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
753 # Use a discrete exit status code of 2 to indicate that a hook action
754 # failed. Users of this script may wish to treat hook action failures
755 # differently from VC failures.
756 print >> sys.stderr, 'Error: %s' % str(e)
757 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000758
maruel@chromium.org0d812442010-08-10 12:41:08 +0000759 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000760 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000761 dependencies = self.dependencies
762 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000763 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000764 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000765 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000766 for i in d.subtree(include_all):
767 yield i
768
769 def depth_first_tree(self):
770 """Depth-first recursion including the root node."""
771 yield self
772 for i in self.dependencies:
773 for j in i.depth_first_tree():
774 if j.should_process:
775 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000776
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000777 @gclient_utils.lockedmethod
778 def add_dependency(self, new_dep):
779 self._dependencies.append(new_dep)
780
781 @gclient_utils.lockedmethod
782 def _mark_as_parsed(self, new_hooks):
783 self._deps_hooks.extend(new_hooks)
784 self._deps_parsed = True
785
maruel@chromium.org68988972011-09-20 14:11:42 +0000786 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000787 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000788 def dependencies(self):
789 return tuple(self._dependencies)
790
791 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000792 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000793 def deps_hooks(self):
794 return tuple(self._deps_hooks)
795
796 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000797 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000798 def parsed_url(self):
799 return self._parsed_url
800
801 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000802 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000803 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000804 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000805 return self._deps_parsed
806
807 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000808 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000809 def processed(self):
810 return self._processed
811
812 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000813 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000814 def hooks_ran(self):
815 return self._hooks_ran
816
817 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000818 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000819 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000820 return tuple(self._file_list)
821
822 @property
823 def file_list_and_children(self):
824 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000825 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000826 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000827 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000828
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000829 def __str__(self):
830 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000831 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000832 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000833 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000834 # First try the native property if it exists.
835 if hasattr(self, '_' + i):
836 value = getattr(self, '_' + i, False)
837 else:
838 value = getattr(self, i, False)
839 if value:
840 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000841
842 for d in self.dependencies:
843 out.extend([' ' + x for x in str(d).splitlines()])
844 out.append('')
845 return '\n'.join(out)
846
847 def __repr__(self):
848 return '%s: %s' % (self.name, self.url)
849
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000850 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000851 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000852 out = '%s(%s)' % (self.name, self.url)
853 i = self.parent
854 while i and i.name:
855 out = '%s(%s) -> %s' % (i.name, i.url, out)
856 i = i.parent
857 return out
858
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000859
860class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000861 """Object that represent a gclient checkout. A tree of Dependency(), one per
862 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000863
864 DEPS_OS_CHOICES = {
865 "win32": "win",
866 "win": "win",
867 "cygwin": "win",
868 "darwin": "mac",
869 "mac": "mac",
870 "unix": "unix",
871 "linux": "unix",
872 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000873 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000874 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000875 }
876
877 DEFAULT_CLIENT_FILE_TEXT = ("""\
878solutions = [
879 { "name" : "%(solution_name)s",
880 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000881 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000882 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000883 "custom_deps" : {
884 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000885 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000886 },
887]
888""")
889
890 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
891 { "name" : "%(solution_name)s",
892 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000893 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000894 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000895 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000896%(solution_deps)s },
897 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000898 },
899""")
900
901 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
902# Snapshot generated with gclient revinfo --snapshot
903solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000904%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000905""")
906
907 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000908 # Do not change previous behavior. Only solution level and immediate DEPS
909 # are processed.
910 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000911 Dependency.__init__(self, None, None, None, None, True, None, None,
912 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000913 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000914 if options.deps_os:
915 enforced_os = options.deps_os.split(',')
916 else:
917 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
918 if 'all' in enforced_os:
919 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000920 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000921 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000922 self.config_content = None
923
924 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000925 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000926 config_dict = {}
927 self.config_content = content
928 try:
929 exec(content, config_dict)
930 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000931 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000932
peter@chromium.org1efccc82012-04-27 16:34:38 +0000933 # Append any target OS that is not already being enforced to the tuple.
934 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000935 if config_dict.get('target_os_only', False):
936 self._enforced_os = tuple(set(target_os))
937 else:
938 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
939
940 if not target_os and config_dict.get('target_os_only', False):
941 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
942 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +0000943
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000944 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000945 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000946 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000947 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000948 self, s['name'], s['url'],
949 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000950 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000951 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000952 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000953 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000954 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000955 except KeyError:
956 raise gclient_utils.Error('Invalid .gclient file. Solution is '
957 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000958 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
959 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000960
961 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000962 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000963 self._options.config_filename),
964 self.config_content)
965
966 @staticmethod
967 def LoadCurrentConfig(options):
968 """Searches for and loads a .gclient file relative to the current working
969 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +0000970 if options.spec:
971 client = GClient('.', options)
972 client.SetConfig(options.spec)
973 else:
974 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
975 if not path:
976 return None
977 client = GClient(path, options)
978 client.SetConfig(gclient_utils.FileRead(
979 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000980
981 if (options.revisions and
982 len(client.dependencies) > 1 and
983 any('@' not in r for r in options.revisions)):
984 print >> sys.stderr, (
985 'You must specify the full solution name like --revision %s@%s\n'
986 'when you have multiple solutions setup in your .gclient file.\n'
987 'Other solutions present are: %s.') % (
988 client.dependencies[0].name,
989 options.revisions[0],
990 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000991 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000992
nsylvain@google.comefc80932011-05-31 21:27:56 +0000993 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000994 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000995 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
996 'solution_name': solution_name,
997 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000998 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000999 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001000 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001001 })
1002
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001003 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001004 """Creates a .gclient_entries file to record the list of unique checkouts.
1005
1006 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001007 """
1008 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1009 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001010 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001011 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001012 # Skip over File() dependencies as we can't version them.
1013 if not isinstance(entry.parsed_url, self.FileImpl):
1014 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1015 pprint.pformat(entry.parsed_url))
1016 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001017 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001018 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001019 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001020
1021 def _ReadEntries(self):
1022 """Read the .gclient_entries file for the given client.
1023
1024 Returns:
1025 A sequence of solution names, which will be empty if there is the
1026 entries file hasn't been created yet.
1027 """
1028 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001029 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001030 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001031 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001032 try:
1033 exec(gclient_utils.FileRead(filename), scope)
1034 except SyntaxError, e:
1035 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001036 return scope['entries']
1037
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001038 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001039 """Checks for revision overrides."""
1040 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001041 if self._options.head:
1042 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001043 # Do not check safesync_url if one or more --revision flag is specified.
1044 if not self._options.revisions:
1045 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001046 if not s.managed:
1047 self._options.revisions.append('%s@unmanaged' % s.name)
1048 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001049 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001050 if not self._options.revisions:
1051 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001052 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001053 index = 0
1054 for revision in self._options.revisions:
1055 if not '@' in revision:
1056 # Support for --revision 123
1057 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001058 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001059 if not sol in solutions_names:
1060 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1061 print >> sys.stderr, ('Please fix your script, having invalid '
1062 '--revision flags will soon considered an error.')
1063 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001064 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001065 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001066 return revision_overrides
1067
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001068 def _ApplySafeSyncRev(self, dep):
1069 """Finds a valid revision from the content of the safesync_url and apply it
1070 by appending revisions to the revision list. Throws if revision appears to
1071 be invalid for the given |dep|."""
1072 assert len(dep.safesync_url) > 0
1073 handle = urllib.urlopen(dep.safesync_url)
1074 rev = handle.read().strip()
1075 handle.close()
1076 if not rev:
1077 raise gclient_utils.Error(
1078 'It appears your safesync_url (%s) is not working properly\n'
1079 '(as it returned an empty response). Check your config.' %
1080 dep.safesync_url)
1081 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
1082 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
1083 if self._options.verbose:
1084 print('Using safesync_url revision: %s.\n' % safe_rev)
1085 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1086
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001087 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001088 """Runs a command on each dependency in a client and its dependencies.
1089
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001090 Args:
1091 command: The command to use (e.g., 'status' or 'diff')
1092 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001093 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001094 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001095 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001096 revision_overrides = {}
1097 # It's unnecessary to check for revision overrides for 'recurse'.
1098 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001099 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001100 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001101 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001102 # Disable progress for non-tty stdout.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001103 if (sys.stdout.isatty() and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001104 if command in ('update', 'revert'):
1105 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001106 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001107 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001108 work_queue = gclient_utils.ExecutionQueue(
1109 self._options.jobs, pm, ignore_requirements=ignore_requirements)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001110 for s in self.dependencies:
1111 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001112 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001113
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001114 # Once all the dependencies have been processed, it's now safe to run the
1115 # hooks.
1116 if not self._options.nohooks:
1117 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001118
1119 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001120 # Notify the user if there is an orphaned entry in their working copy.
1121 # Only delete the directory if there are no changes in it, and
1122 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001123 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001124 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001125 if not prev_url:
1126 # entry must have been overridden via .gclient custom_deps
1127 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001128 # Fix path separator on Windows.
1129 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001130 e_dir = os.path.join(self.root_dir, entry_fixed)
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001131
1132 def _IsParentOfAny(parent, path_list):
1133 parent_plus_slash = parent + '/'
1134 return any(
1135 path[:len(parent_plus_slash)] == parent_plus_slash
1136 for path in path_list)
1137
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001138 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001139 if (entry not in entries and
1140 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001141 os.path.exists(e_dir)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001142 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001143 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001144 scm.status(self._options, [], file_list)
1145 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001146 if (not self._options.delete_unversioned_trees or
1147 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001148 # There are modified files in this entry. Keep warning until
1149 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001150 print(('\nWARNING: \'%s\' is no longer part of this client. '
1151 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001152 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001153 else:
1154 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001155 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001156 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001157 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001158 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001159 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001160 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001161
1162 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001163 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001164 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001165 # Load all the settings.
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001166 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None, False)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001167 for s in self.dependencies:
1168 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001169 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001170
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001171 def GetURLAndRev(dep):
1172 """Returns the revision-qualified SCM url for a Dependency."""
1173 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001174 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001175 if isinstance(dep.parsed_url, self.FileImpl):
1176 original_url = dep.parsed_url.file_location
1177 else:
1178 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001179 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001180 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001181 if not os.path.isdir(scm.checkout_path):
1182 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001183 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001184
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001185 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001186 new_gclient = ''
1187 # First level at .gclient
1188 for d in self.dependencies:
1189 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001190 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001191 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001192 for d in dep.dependencies:
1193 entries[d.name] = GetURLAndRev(d)
1194 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001195 GrabDeps(d)
1196 custom_deps = []
1197 for k in sorted(entries.keys()):
1198 if entries[k]:
1199 # Quotes aren't escaped...
1200 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1201 else:
1202 custom_deps.append(' \"%s\": None,\n' % k)
1203 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1204 'solution_name': d.name,
1205 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001206 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001207 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001208 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001209 'solution_deps': ''.join(custom_deps),
1210 }
1211 # Print the snapshot configuration file
1212 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001213 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001214 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001215 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001216 if self._options.actual:
1217 entries[d.name] = GetURLAndRev(d)
1218 else:
1219 entries[d.name] = d.parsed_url
1220 keys = sorted(entries.keys())
1221 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001222 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001223 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001224
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001225 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001226 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001227 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001228
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001229 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001230 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001231 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001232 return self._root_dir
1233
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001234 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001235 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001236 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001237 return self._enforced_os
1238
maruel@chromium.org68988972011-09-20 14:11:42 +00001239 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001240 def recursion_limit(self):
1241 """How recursive can each dependencies in DEPS file can load DEPS file."""
1242 return self._recursion_limit
1243
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001244 @property
1245 def target_os(self):
1246 return self._enforced_os
1247
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001248
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001249#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001250
1251
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001252def CMDcleanup(parser, args):
1253 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001254
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001255Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001256"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001257 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1258 help='override deps for the specified (comma-separated) '
1259 'platform(s); \'all\' will process all deps_os '
1260 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001261 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001262 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001263 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001264 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001265 if options.verbose:
1266 # Print out the .gclient file. This is longer than if we just printed the
1267 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001268 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001269 return client.RunOnDeps('cleanup', args)
1270
1271
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001272@attr('usage', '[command] [args ...]')
1273def CMDrecurse(parser, args):
1274 """Operates on all the entries.
1275
1276 Runs a shell command on all entries.
ilevy@chromium.org37116242012-11-28 01:32:48 +00001277 Sets GCLIENT_DEP_PATH enviroment variable as the dep's relative location to
1278 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001279 """
1280 # Stop parsing at the first non-arg so that these go through to the command
1281 parser.disable_interspersed_args()
1282 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001283 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001284 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001285 help='Ignore non-zero return codes from subcommands.')
1286 parser.add_option('--prepend-dir', action='store_true',
1287 help='Prepend relative dir for use with git <cmd> --null.')
1288 parser.add_option('--no-progress', action='store_true',
1289 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001290 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001291 if not args:
1292 print >> sys.stderr, 'Need to supply a command!'
1293 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001294 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1295 if not root_and_entries:
1296 print >> sys.stderr, (
1297 'You need to run gclient sync at least once to use \'recurse\'.\n'
1298 'This is because .gclient_entries needs to exist and be up to date.')
1299 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001300
1301 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001302 scm_set = set()
1303 for scm in options.scm:
1304 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001305 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001306
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001307 options.nohooks = True
1308 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001309 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1310 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001311
1312
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001313@attr('usage', '[args ...]')
1314def CMDfetch(parser, args):
1315 """Fetches upstream commits for all modules.
1316
1317Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1318"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001319 (options, args) = parser.parse_args(args)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001320 return CMDrecurse(Parser(), [
1321 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1322
1323
1324def CMDgrep(parser, args):
1325 """Greps through git repos managed by gclient.
1326
1327Runs 'git grep [args...]' for each module.
1328"""
1329
1330 # We can't use optparse because it will try to parse arguments sent
1331 # to git grep and throw an error. :-(
1332 if not args or re.match('(-h|--help)$', args[0]):
1333 print >> sys.stderr, (
1334 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1335 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1336 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1337 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
1338 ' end of your query.'
1339 )
1340 return 1
1341
1342 jobs_arg = ['--jobs=1']
1343 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1344 jobs_arg, args = args[:1], args[1:]
1345 elif re.match(r'(-j|--jobs)$', args[0]):
1346 jobs_arg, args = args[:2], args[2:]
1347
1348 return CMDrecurse(
1349 parser,
1350 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1351 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001352
1353
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001354@attr('usage', '[url] [safesync url]')
1355def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001356 """Create a .gclient file in the current directory.
1357
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001358This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001359top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001360modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001361provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001362URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001363"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001364
1365 # We do a little dance with the --gclientfile option. 'gclient config' is the
1366 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1367 # arguments. So, we temporarily stash any --gclientfile parameter into
1368 # options.output_config_file until after the (gclientfile xor spec) error
1369 # check.
1370 parser.remove_option('--gclientfile')
1371 parser.add_option('--gclientfile', dest='output_config_file',
1372 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001373 parser.add_option('--name',
1374 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001375 parser.add_option('--deps-file', default='DEPS',
1376 help='overrides the default name for the DEPS file for the'
1377 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001378 parser.add_option('--unmanaged', action='store_true', default=False,
1379 help='overrides the default behavior to make it possible '
1380 'to have the main solution untouched by gclient '
1381 '(gclient will check out unmanaged dependencies but '
1382 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001383 parser.add_option('--git-deps', action='store_true',
1384 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001385 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001386 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001387 if options.output_config_file:
1388 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001389 if ((options.spec and args) or len(args) > 2 or
1390 (not options.spec and not args)):
1391 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1392
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001393 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001394 if options.spec:
1395 client.SetConfig(options.spec)
1396 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001397 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001398 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001399 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001400 if name.endswith('.git'):
1401 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001402 else:
1403 # specify an alternate relpath for the given URL.
1404 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001405 deps_file = options.deps_file
1406 if options.git_deps:
1407 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001408 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001409 if len(args) > 1:
1410 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001411 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1412 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001413 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001414 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001415
1416
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001417@attr('epilog', """Example:
1418 gclient pack > patch.txt
1419 generate simple patch for configured client and dependences
1420""")
1421def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001422 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001423
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001424Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001425dependencies, and performs minimal postprocessing of the output. The
1426resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001427checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001428"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001429 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1430 help='override deps for the specified (comma-separated) '
1431 'platform(s); \'all\' will process all deps_os '
1432 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001433 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001434 (options, args) = parser.parse_args(args)
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001435 # Force jobs to 1 so the stdout is not annotated with the thread ids
1436 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001437 client = GClient.LoadCurrentConfig(options)
1438 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001439 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001440 if options.verbose:
1441 # Print out the .gclient file. This is longer than if we just printed the
1442 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001443 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001444 return client.RunOnDeps('pack', args)
1445
1446
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001447def CMDstatus(parser, args):
1448 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001449 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1450 help='override deps for the specified (comma-separated) '
1451 'platform(s); \'all\' will process all deps_os '
1452 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001453 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001454 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001455 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001456 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001457 if options.verbose:
1458 # Print out the .gclient file. This is longer than if we just printed the
1459 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001460 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001461 return client.RunOnDeps('status', args)
1462
1463
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001464@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001465 gclient sync
1466 update files from SCM according to current configuration,
1467 *for modules which have changed since last update or sync*
1468 gclient sync --force
1469 update files from SCM according to current configuration, for
1470 all modules (useful for recovering files deleted from local copy)
1471 gclient sync --revision src@31000
1472 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001473""")
1474def CMDsync(parser, args):
1475 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001476 parser.add_option('-f', '--force', action='store_true',
1477 help='force update even for unchanged modules')
1478 parser.add_option('-n', '--nohooks', action='store_true',
1479 help='don\'t run hooks after the update is complete')
1480 parser.add_option('-r', '--revision', action='append',
1481 dest='revisions', metavar='REV', default=[],
1482 help='Enforces revision/hash for the solutions with the '
1483 'format src@rev. The src@ part is optional and can be '
1484 'skipped. -r can be used multiple times when .gclient '
1485 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001486 'if the src@ part is skipped. Note that specifying '
1487 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001488 parser.add_option('--with_branch_heads', action='store_true',
1489 help='Clone git "branch_heads" refspecs in addition to '
1490 'the default refspecs. This adds about 1/2GB to a '
1491 'full checkout. (git only)')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001492 parser.add_option('-t', '--transitive', action='store_true',
1493 help='When a revision is specified (in the DEPS file or '
1494 'with the command-line flag), transitively update '
1495 'the dependencies to the date of the given revision. '
1496 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001497 parser.add_option('-H', '--head', action='store_true',
1498 help='skips any safesync_urls specified in '
1499 'configured solutions and sync to head instead')
1500 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001501 help='Deletes from the working copy any dependencies that '
1502 'have been removed since the last sync, as long as '
1503 'there are no local modifications. When used with '
1504 '--force, such dependencies are removed even if they '
1505 'have local modifications. When used with --reset, '
1506 'all untracked directories are removed from the '
1507 'working copy, exclusing those which are explicitly '
1508 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001509 parser.add_option('-R', '--reset', action='store_true',
1510 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001511 parser.add_option('-M', '--merge', action='store_true',
1512 help='merge upstream changes instead of trying to '
1513 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001514 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1515 help='override deps for the specified (comma-separated) '
1516 'platform(s); \'all\' will process all deps_os '
1517 'references')
1518 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1519 help='Skip svn up whenever possible by requesting '
1520 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001521 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001522 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001523
1524 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001525 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001526
maruel@chromium.org307d1792010-05-31 20:03:13 +00001527 if options.revisions and options.head:
1528 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001529 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001530
1531 if options.verbose:
1532 # Print out the .gclient file. This is longer than if we just printed the
1533 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001534 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001535 return client.RunOnDeps('update', args)
1536
1537
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001538def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001539 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001540 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001541
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001542def CMDdiff(parser, args):
1543 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001544 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1545 help='override deps for the specified (comma-separated) '
1546 'platform(s); \'all\' will process all deps_os '
1547 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001548 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001549 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001550 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001551 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001552 if options.verbose:
1553 # Print out the .gclient file. This is longer than if we just printed the
1554 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001555 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001556 return client.RunOnDeps('diff', args)
1557
1558
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001559def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001560 """Revert all modifications in every dependencies.
1561
1562 That's the nuclear option to get back to a 'clean' state. It removes anything
1563 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001564 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1565 help='override deps for the specified (comma-separated) '
1566 'platform(s); \'all\' will process all deps_os '
1567 'references')
1568 parser.add_option('-n', '--nohooks', action='store_true',
1569 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001570 (options, args) = parser.parse_args(args)
1571 # --force is implied.
1572 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001573 options.reset = False
1574 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001575 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001576 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001577 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001578 return client.RunOnDeps('revert', args)
1579
1580
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001581def CMDrunhooks(parser, args):
1582 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001583 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1584 help='override deps for the specified (comma-separated) '
1585 'platform(s); \'all\' will process all deps_os '
1586 'references')
1587 parser.add_option('-f', '--force', action='store_true', default=True,
1588 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001589 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001590 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001591 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001592 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001593 if options.verbose:
1594 # Print out the .gclient file. This is longer than if we just printed the
1595 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001596 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001597 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001598 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001599 return client.RunOnDeps('runhooks', args)
1600
1601
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001602def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001603 """Output revision info mapping for the client and its dependencies.
1604
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001605 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001606 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001607 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1608 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001609 commit can change.
1610 """
1611 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1612 help='override deps for the specified (comma-separated) '
1613 'platform(s); \'all\' will process all deps_os '
1614 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001615 parser.add_option('-a', '--actual', action='store_true',
1616 help='gets the actual checked out revisions instead of the '
1617 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001618 parser.add_option('-s', '--snapshot', action='store_true',
1619 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001620 'version of all repositories to reproduce the tree, '
1621 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001622 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001623 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001624 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001625 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001626 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001627 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001628
1629
szager@google.comb9a78d32012-03-13 18:46:21 +00001630def CMDhookinfo(parser, args):
1631 """Output the hooks that would be run by `gclient runhooks`"""
1632 (options, args) = parser.parse_args(args)
1633 options.force = True
1634 client = GClient.LoadCurrentConfig(options)
1635 if not client:
1636 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1637 client.RunOnDeps(None, [])
1638 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1639 return 0
1640
1641
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001642def Command(name):
1643 return getattr(sys.modules[__name__], 'CMD' + name, None)
1644
1645
1646def CMDhelp(parser, args):
1647 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001648 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001649 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001650 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001651 parser.print_help()
1652 return 0
1653
1654
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001655def GenUsage(parser, command):
1656 """Modify an OptParse object with the function's documentation."""
1657 obj = Command(command)
1658 if command == 'help':
1659 command = '<command>'
1660 # OptParser.description prefer nicely non-formatted strings.
1661 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1662 usage = getattr(obj, 'usage', '')
1663 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1664 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001665
1666
maruel@chromium.org0895b752011-08-26 20:40:33 +00001667def Parser():
1668 """Returns the default parser."""
1669 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001670 # some arm boards have issues with parallel sync.
1671 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001672 jobs = 1
1673 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001674 jobs = max(8, gclient_utils.NumLocalCpus())
szager@chromium.orge2e03202012-07-31 18:05:16 +00001675 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001676 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001677 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001678 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001679 parser.add_option('-v', '--verbose', action='count', default=0,
1680 help='Produces additional output for diagnostics. Can be '
1681 'used up to three times for more logging info.')
1682 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001683 default=None,
1684 help='Specify an alternate %s file' % gclientfile_default)
1685 parser.add_option('--spec',
1686 default=None,
1687 help='create a gclient file containing the provided '
1688 'string. Due to Cygwin/Python brokenness, it '
1689 'probably can\'t contain any newlines.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001690 # Integrate standard options processing.
1691 old_parser = parser.parse_args
1692 def Parse(args):
1693 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001694 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1695 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001696 logging.basicConfig(level=level,
1697 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001698 if options.config_filename and options.spec:
1699 parser.error('Cannot specifiy both --gclientfile and --spec')
1700 if not options.config_filename:
1701 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001702 options.entries_filename = options.config_filename + '_entries'
1703 if options.jobs < 1:
1704 parser.error('--jobs must be 1 or higher')
1705
1706 # These hacks need to die.
1707 if not hasattr(options, 'revisions'):
1708 # GClient.RunOnDeps expects it even if not applicable.
1709 options.revisions = []
1710 if not hasattr(options, 'head'):
1711 options.head = None
1712 if not hasattr(options, 'nohooks'):
1713 options.nohooks = True
1714 if not hasattr(options, 'deps_os'):
1715 options.deps_os = None
1716 if not hasattr(options, 'manually_grab_svn_rev'):
1717 options.manually_grab_svn_rev = None
1718 if not hasattr(options, 'force'):
1719 options.force = None
1720 return (options, args)
1721 parser.parse_args = Parse
1722 # We don't want wordwrapping in epilog (usually examples)
1723 parser.format_epilog = lambda _: parser.epilog or ''
1724 return parser
1725
1726
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001727def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001728 """Doesn't parse the arguments here, just find the right subcommand to
1729 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001730 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001731 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001732 '\nYour python version %s is unsupported, please upgrade.\n' %
1733 sys.version.split(' ', 1)[0])
1734 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00001735 if not sys.executable:
1736 print >> sys.stderr, (
1737 '\nPython cannot find the location of it\'s own executable.\n')
1738 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001739 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001740 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001741 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1742 # operations. Python as a strong tendency to buffer sys.stdout.
1743 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001744 # Make stdout annotated with the thread ids.
1745 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001746 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001747 # Unused variable 'usage'
1748 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001749 def to_str(fn):
1750 return (
1751 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1752 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1753 cmds = (
1754 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1755 )
1756 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001757 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001758 if argv:
1759 command = Command(argv[0])
1760 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001761 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001762 GenUsage(parser, argv[0])
1763 return command(parser, argv[1:])
1764 # Not a known command. Default to help.
1765 GenUsage(parser, 'help')
1766 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001767 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001768 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001769 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001770
1771
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001772if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001773 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001774 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001775
1776# vim: ts=2:sw=2:tw=80:et: