blob: a91c61b32bf45253a9bedf9a8f39b909ec317bdb [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" ]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000058"""
59
maruel@chromium.org82798cb2012-02-23 18:16:12 +000060__version__ = "0.6.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000062import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000063import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000064import optparse
65import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000066import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000067import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000068import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000069import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000072import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000074import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000075
maruel@chromium.org35625c72011-03-23 17:34:02 +000076import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000077import gclient_scm
78import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000079from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000080import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +000081from third_party import colorama
82# Import shortcut.
83from third_party.colorama import Fore
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000086def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000087 """Sets an attribute on a function."""
88 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000089 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000090 return fn
91 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000092
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000094## GClient implementation.
95
96
maruel@chromium.org116704f2010-06-11 17:34:38 +000097class GClientKeywords(object):
98 class FromImpl(object):
99 """Used to implement the From() syntax."""
100
101 def __init__(self, module_name, sub_target_name=None):
102 """module_name is the dep module we want to include from. It can also be
103 the name of a subdirectory to include from.
104
105 sub_target_name is an optional parameter if the module name in the other
106 DEPS file is different. E.g., you might want to map src/net to net."""
107 self.module_name = module_name
108 self.sub_target_name = sub_target_name
109
110 def __str__(self):
111 return 'From(%s, %s)' % (repr(self.module_name),
112 repr(self.sub_target_name))
113
maruel@chromium.org116704f2010-06-11 17:34:38 +0000114 class FileImpl(object):
115 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000116 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000117
118 def __init__(self, file_location):
119 self.file_location = file_location
120
121 def __str__(self):
122 return 'File("%s")' % self.file_location
123
124 def GetPath(self):
125 return os.path.split(self.file_location)[0]
126
127 def GetFilename(self):
128 rev_tokens = self.file_location.split('@')
129 return os.path.split(rev_tokens[0])[1]
130
131 def GetRevision(self):
132 rev_tokens = self.file_location.split('@')
133 if len(rev_tokens) > 1:
134 return rev_tokens[1]
135 return None
136
137 class VarImpl(object):
138 def __init__(self, custom_vars, local_scope):
139 self._custom_vars = custom_vars
140 self._local_scope = local_scope
141
142 def Lookup(self, var_name):
143 """Implements the Var syntax."""
144 if var_name in self._custom_vars:
145 return self._custom_vars[var_name]
146 elif var_name in self._local_scope.get("vars", {}):
147 return self._local_scope["vars"][var_name]
148 raise gclient_utils.Error("Var is not defined: %s" % var_name)
149
150
maruel@chromium.org064186c2011-09-27 23:53:33 +0000151class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000152 """Immutable configuration settings."""
153 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000154 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000155 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000156 GClientKeywords.__init__(self)
157
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000158 # These are not mutable:
159 self._parent = parent
160 self._safesync_url = safesync_url
161 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000162 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000163 # 'managed' determines whether or not this dependency is synced/updated by
164 # gclient after gclient checks it out initially. The difference between
165 # 'managed' and 'should_process' is that the user specifies 'managed' via
166 # the --unmanaged command-line flag or a .gclient config, where
167 # 'should_process' is dynamically set by gclient if it goes over its
168 # recursion limit and controls gclient's behavior so it does not misbehave.
169 self._managed = managed
170 self._should_process = should_process
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000171 # This is a mutable value that overrides the normal recursion limit for this
172 # dependency. It is read from the actual DEPS file so cannot be set on
173 # class instantiation.
174 self.recursion_override = None
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000175 # This is a mutable value which has the list of 'target_os' OSes listed in
176 # the current deps file.
177 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000178
179 # These are only set in .gclient and not in DEPS files.
180 self._custom_vars = custom_vars or {}
181 self._custom_deps = custom_deps or {}
182
maruel@chromium.org064186c2011-09-27 23:53:33 +0000183 # Post process the url to remove trailing slashes.
184 if isinstance(self._url, basestring):
185 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
186 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000187 self._url = self._url.replace('/@', '@')
188 elif not isinstance(self._url,
189 (self.FromImpl, self.FileImpl, None.__class__)):
190 raise gclient_utils.Error(
191 ('dependency url must be either a string, None, '
192 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000193 if '/' in self._deps_file or '\\' in self._deps_file:
194 raise gclient_utils.Error('deps_file name must not be a path, just a '
195 'filename. %s' % self._deps_file)
196
197 @property
198 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000199 return self._deps_file
200
201 @property
202 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000203 return self._managed
204
205 @property
206 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000207 return self._parent
208
209 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000210 def root(self):
211 """Returns the root node, a GClient object."""
212 if not self.parent:
213 # This line is to signal pylint that it could be a GClient instance.
214 return self or GClient(None, None)
215 return self.parent.root
216
217 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000219 return self._safesync_url
220
221 @property
222 def should_process(self):
223 """True if this dependency should be processed, i.e. checked out."""
224 return self._should_process
225
226 @property
227 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000228 return self._custom_vars.copy()
229
230 @property
231 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000232 return self._custom_deps.copy()
233
maruel@chromium.org064186c2011-09-27 23:53:33 +0000234 @property
235 def url(self):
236 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000237
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000238 @property
239 def recursion_limit(self):
240 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000241 if self.recursion_override is not None:
242 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000243 return max(self.parent.recursion_limit - 1, 0)
244
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000245 @property
246 def target_os(self):
247 if self.local_target_os is not None:
248 return tuple(set(self.local_target_os).union(self.parent.target_os))
249 else:
250 return self.parent.target_os
251
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000252 def get_custom_deps(self, name, url):
253 """Returns a custom deps if applicable."""
254 if self.parent:
255 url = self.parent.get_custom_deps(name, url)
256 # None is a valid return value to disable a dependency.
257 return self.custom_deps.get(name, url)
258
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259
260class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000261 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000262
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000263 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000264 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000265 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000266 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000267 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000268 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000269
270 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000271 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000272
273 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000275 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000276 # A cache of the files affected by the current operation, necessary for
277 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000278 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000279 # If it is not set to True, the dependency wasn't processed for its child
280 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000281 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000282 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000283 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000284 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000285 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000286
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000287 if not self.name and self.parent:
288 raise gclient_utils.Error('Dependency without name')
289
maruel@chromium.org470b5432011-10-11 18:18:19 +0000290 @property
291 def requirements(self):
292 """Calculate the list of requirements."""
293 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000294 # self.parent is implicitly a requirement. This will be recursive by
295 # definition.
296 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000297 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000298
299 # For a tree with at least 2 levels*, the leaf node needs to depend
300 # on the level higher up in an orderly way.
301 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
302 # thus unsorted, while the .gclient format is a list thus sorted.
303 #
304 # * _recursion_limit is hard coded 2 and there is no hope to change this
305 # value.
306 #
307 # Interestingly enough, the following condition only works in the case we
308 # want: self is a 2nd level node. 3nd level node wouldn't need this since
309 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000310 if self.parent and self.parent.parent and not self.parent.parent.parent:
311 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000312
313 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000314 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000315
maruel@chromium.org470b5432011-10-11 18:18:19 +0000316 if self.name:
317 requirements |= set(
318 obj.name for obj in self.root.subtree(False)
319 if (obj is not self
320 and obj.name and
321 self.name.startswith(posixpath.join(obj.name, ''))))
322 requirements = tuple(sorted(requirements))
323 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
324 return requirements
325
326 def verify_validity(self):
327 """Verifies that this Dependency is fine to add as a child of another one.
328
329 Returns True if this entry should be added, False if it is a duplicate of
330 another entry.
331 """
332 logging.info('Dependency(%s).verify_validity()' % self.name)
333 if self.name in [s.name for s in self.parent.dependencies]:
334 raise gclient_utils.Error(
335 'The same name "%s" appears multiple times in the deps section' %
336 self.name)
337 if not self.should_process:
338 # Return early, no need to set requirements.
339 return True
340
341 # This require a full tree traversal with locks.
342 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
343 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000344 self_url = self.LateOverride(self.url)
345 sibling_url = sibling.LateOverride(sibling.url)
346 # Allow to have only one to be None or ''.
347 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000348 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000349 ('Dependency %s specified more than once:\n'
350 ' %s [%s]\n'
351 'vs\n'
352 ' %s [%s]') % (
353 self.name,
354 sibling.hierarchy(),
355 sibling_url,
356 self.hierarchy(),
357 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000358 # In theory we could keep it as a shadow of the other one. In
359 # practice, simply ignore it.
360 logging.warn('Won\'t process duplicate dependency %s' % sibling)
361 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000362 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000363
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000364 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000365 """Resolves the parsed url from url.
366
367 Manages From() keyword accordingly. Do not touch self.parsed_url nor
368 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000369 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000370 parsed_url = self.get_custom_deps(self.name, url)
371 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000372 logging.info(
373 'Dependency(%s).LateOverride(%s) -> %s' %
374 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000375 return parsed_url
376
377 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000378 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000379 ref = [
380 dep for dep in self.root.subtree(True) if url.module_name == dep.name
381 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000382 if not ref:
383 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
384 url.module_name, ref))
385 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000386 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000387 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000388 found_deps = [d for d in ref.dependencies if d.name == sub_target]
389 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000390 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000391 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
392 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000393 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000394
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000395 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000396 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000397 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000398 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000399 'Dependency(%s).LateOverride(%s) -> %s (From)' %
400 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000401 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000402
403 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000404 parsed_url = urlparse.urlparse(url)
405 if not parsed_url[0]:
406 # A relative url. Fetch the real base.
407 path = parsed_url[2]
408 if not path.startswith('/'):
409 raise gclient_utils.Error(
410 'relative DEPS entry \'%s\' must begin with a slash' % url)
411 # Create a scm just to query the full url.
412 parent_url = self.parent.parsed_url
413 if isinstance(parent_url, self.FileImpl):
414 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000415 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000416 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000417 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000418 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000419 logging.info(
420 'Dependency(%s).LateOverride(%s) -> %s' %
421 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000422 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000423
424 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000425 logging.info(
426 'Dependency(%s).LateOverride(%s) -> %s (File)' %
427 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000428 return url
429
430 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000431 logging.info(
432 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000433 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000434
435 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000436
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000437 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000438 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000439 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000440 assert not self.dependencies
441 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000442 local_scope = {}
443 var = self.VarImpl(self.custom_vars, local_scope)
444 global_scope = {
445 'File': self.FileImpl,
446 'From': self.FromImpl,
447 'Var': var.Lookup,
448 'deps_os': {},
449 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000450 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000451 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000452 logging.info(
453 'ParseDepsFile(%s): No %s file found at %s' % (
454 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000455 else:
456 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000457 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000458 # Eval the content.
459 try:
460 exec(deps_content, global_scope, local_scope)
461 except SyntaxError, e:
462 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000463 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000464 if 'recursion' in local_scope:
465 self.recursion_override = local_scope.get('recursion')
466 logging.warning(
467 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000468 # If present, save 'target_os' in the local_target_os property.
469 if 'target_os' in local_scope:
470 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000471 # load os specific dependencies if defined. these dependencies may
472 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000473 if 'deps_os' in local_scope:
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000474 for deps_os_key in self.target_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000475 os_deps = local_scope['deps_os'].get(deps_os_key, {})
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000476 if len(self.target_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000477 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000478 # platform, so we collect the broadest set of dependencies
479 # available. We may end up with the wrong revision of something for
480 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000481 deps.update([x for x in os_deps.items() if not x[0] in deps])
482 else:
483 deps.update(os_deps)
484
maruel@chromium.org271375b2010-06-23 19:17:38 +0000485 # If a line is in custom_deps, but not in the solution, we want to append
486 # this line to the solution.
487 for d in self.custom_deps:
488 if d not in deps:
489 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000490
491 # If use_relative_paths is set in the DEPS file, regenerate
492 # the dictionary using paths relative to the directory containing
493 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000494 use_relative_paths = local_scope.get('use_relative_paths', False)
495 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000496 rel_deps = {}
497 for d, url in deps.items():
498 # normpath is required to allow DEPS to use .. in their
499 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000500 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
501 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000502
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000503 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000504 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000505 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000506 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000507 deps_to_add.append(Dependency(
508 self, name, url, None, None, None, None,
509 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000510 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000511 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
512 logging.info('ParseDepsFile(%s) done' % self.name)
513
514 def add_dependencies_and_close(self, deps_to_add, hooks):
515 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000516 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000517 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000518 self.add_dependency(dep)
519 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000520
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000521 @staticmethod
522 def maybeGetParentRevision(
523 command, options, parsed_url, parent_name, revision_overrides):
524 """If we are performing an update and --transitive is set, set the
525 revision to the parent's revision. If we have an explicit revision
526 do nothing."""
527 if command == 'update' and options.transitive and not options.revision:
528 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
529 if not revision:
530 options.revision = revision_overrides.get(parent_name)
531 if options.verbose and options.revision:
532 print("Using parent's revision date: %s" % options.revision)
533 # If the parent has a revision override, then it must have been
534 # converted to date format.
535 assert (not options.revision or
536 gclient_utils.IsDateRevision(options.revision))
537
538 @staticmethod
539 def maybeConvertToDateRevision(
540 command, options, name, scm, revision_overrides):
541 """If we are performing an update and --transitive is set, convert the
542 revision to a date-revision (if necessary). Instead of having
543 -r 101 replace the revision with the time stamp of 101 (e.g.
544 "{2011-18-04}").
545 This way dependencies are upgraded to the revision they had at the
546 check-in of revision 101."""
547 if (command == 'update' and
548 options.transitive and
549 options.revision and
550 not gclient_utils.IsDateRevision(options.revision)):
551 revision_date = scm.GetRevisionDate(options.revision)
552 revision = gclient_utils.MakeDateRevision(revision_date)
553 if options.verbose:
554 print("Updating revision override from %s to %s." %
555 (options.revision, revision))
556 revision_overrides[name] = revision
557
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000558 # Arguments number differs from overridden method
559 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000560 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000561 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000562 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000563 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000564 if not self.should_process:
565 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000566 # When running runhooks, there's no need to consult the SCM.
567 # All known hooks are expected to run unconditionally regardless of working
568 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000569 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000570 parsed_url = self.LateOverride(self.url)
571 file_list = []
572 if run_scm and parsed_url:
573 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000574 # Special support for single-file checkout.
575 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000576 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
577 # pylint: disable=E1103
578 options.revision = parsed_url.GetRevision()
579 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000580 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000581 self.name)
582 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000583 args + [parsed_url.GetFilename()],
584 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000585 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000586 # Create a shallow copy to mutate revision.
587 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000588 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000589 self.maybeGetParentRevision(
590 command, options, parsed_url, self.parent.name, revision_overrides)
591 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
592 scm.RunCommand(command, options, args, file_list)
593 self.maybeConvertToDateRevision(
594 command, options, self.name, scm, revision_overrides)
595 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000596
597 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
598 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000599 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000600 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000601 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000602 continue
603 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000604 [self.root.root_dir.lower(), file_list[i].lower()])
605 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000606 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000607 while file_list[i].startswith(('\\', '/')):
608 file_list[i] = file_list[i][1:]
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +0000609 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000610 if not isinstance(parsed_url, self.FileImpl):
611 # Skip file only checkout.
612 scm = gclient_scm.GetScmName(parsed_url)
613 if not options.scm or scm in options.scm:
614 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
615 # Pass in the SCM type as an env variable
616 env = os.environ.copy()
617 if scm:
618 env['GCLIENT_SCM'] = scm
619 if parsed_url:
620 env['GCLIENT_URL'] = parsed_url
621 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000622 try:
623 gclient_utils.CheckCallAndFilter(
624 args, cwd=cwd, env=env, print_stdout=True)
625 except subprocess2.CalledProcessError:
626 if not options.ignore:
627 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000628 else:
629 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000630
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000631 # Always parse the DEPS file.
632 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000633
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000634 self._run_is_done(file_list, parsed_url)
635
636 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000637 # Parse the dependencies of this dependency.
638 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000639 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000640
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000641 @gclient_utils.lockedmethod
642 def _run_is_done(self, file_list, parsed_url):
643 # Both these are kept for hooks that are run as a separate tree traversal.
644 self._file_list = file_list
645 self._parsed_url = parsed_url
646 self._processed = True
647
szager@google.comb9a78d32012-03-13 18:46:21 +0000648 @staticmethod
649 def GetHookAction(hook_dict, matching_file_list):
650 """Turns a parsed 'hook' dict into an executable command."""
651 logging.debug(hook_dict)
652 logging.debug(matching_file_list)
653 command = hook_dict['action'][:]
654 if command[0] == 'python':
655 # If the hook specified "python" as the first item, the action is a
656 # Python script. Run it by starting a new copy of the same
657 # interpreter.
658 command[0] = sys.executable
659 if '$matching_files' in command:
660 splice_index = command.index('$matching_files')
661 command[splice_index:splice_index + 1] = matching_file_list
662 return command
663
664 def GetHooks(self, options):
665 """Evaluates all hooks, and return them in a flat list.
666
667 RunOnDeps() must have been called before to load the DEPS.
668 """
669 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000670 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000671 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000672 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000673 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000674 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000675 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000676 # TODO(maruel): If the user is using git or git-svn, then we don't know
677 # what files have changed so we always run all hooks. It'd be nice to fix
678 # that.
679 if (options.force or
680 isinstance(self.parsed_url, self.FileImpl) or
681 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000682 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000683 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000684 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000685 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000686 # Run hooks on the basis of whether the files from the gclient operation
687 # match each hook's pattern.
688 for hook_dict in self.deps_hooks:
689 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000690 matching_file_list = [
691 f for f in self.file_list_and_children if pattern.search(f)
692 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000693 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000694 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000695 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000696 result.extend(s.GetHooks(options))
697 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000698
szager@google.comb9a78d32012-03-13 18:46:21 +0000699 def RunHooksRecursively(self, options):
700 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000701 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000702 for hook in self.GetHooks(options):
703 try:
704 gclient_utils.CheckCallAndFilterAndHeader(
705 hook, cwd=self.root.root_dir, always=True)
706 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
707 # Use a discrete exit status code of 2 to indicate that a hook action
708 # failed. Users of this script may wish to treat hook action failures
709 # differently from VC failures.
710 print >> sys.stderr, 'Error: %s' % str(e)
711 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000712
maruel@chromium.org0d812442010-08-10 12:41:08 +0000713 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000714 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000715 dependencies = self.dependencies
716 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000717 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000718 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000719 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000720 for i in d.subtree(include_all):
721 yield i
722
723 def depth_first_tree(self):
724 """Depth-first recursion including the root node."""
725 yield self
726 for i in self.dependencies:
727 for j in i.depth_first_tree():
728 if j.should_process:
729 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000730
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000731 @gclient_utils.lockedmethod
732 def add_dependency(self, new_dep):
733 self._dependencies.append(new_dep)
734
735 @gclient_utils.lockedmethod
736 def _mark_as_parsed(self, new_hooks):
737 self._deps_hooks.extend(new_hooks)
738 self._deps_parsed = True
739
maruel@chromium.org68988972011-09-20 14:11:42 +0000740 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000741 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000742 def dependencies(self):
743 return tuple(self._dependencies)
744
745 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000746 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000747 def deps_hooks(self):
748 return tuple(self._deps_hooks)
749
750 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000751 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000752 def parsed_url(self):
753 return self._parsed_url
754
755 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000756 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000757 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000758 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000759 return self._deps_parsed
760
761 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000762 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000763 def processed(self):
764 return self._processed
765
766 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000767 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000768 def hooks_ran(self):
769 return self._hooks_ran
770
771 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000772 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000773 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000774 return tuple(self._file_list)
775
776 @property
777 def file_list_and_children(self):
778 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000779 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000780 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000781 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000782
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000783 def __str__(self):
784 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000785 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000786 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000787 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000788 # First try the native property if it exists.
789 if hasattr(self, '_' + i):
790 value = getattr(self, '_' + i, False)
791 else:
792 value = getattr(self, i, False)
793 if value:
794 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000795
796 for d in self.dependencies:
797 out.extend([' ' + x for x in str(d).splitlines()])
798 out.append('')
799 return '\n'.join(out)
800
801 def __repr__(self):
802 return '%s: %s' % (self.name, self.url)
803
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000804 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000805 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000806 out = '%s(%s)' % (self.name, self.url)
807 i = self.parent
808 while i and i.name:
809 out = '%s(%s) -> %s' % (i.name, i.url, out)
810 i = i.parent
811 return out
812
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000813
814class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000815 """Object that represent a gclient checkout. A tree of Dependency(), one per
816 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000817
818 DEPS_OS_CHOICES = {
819 "win32": "win",
820 "win": "win",
821 "cygwin": "win",
822 "darwin": "mac",
823 "mac": "mac",
824 "unix": "unix",
825 "linux": "unix",
826 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000827 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000828 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000829 }
830
831 DEFAULT_CLIENT_FILE_TEXT = ("""\
832solutions = [
833 { "name" : "%(solution_name)s",
834 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000835 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000836 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000837 "custom_deps" : {
838 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000839 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000840 },
841]
842""")
843
844 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
845 { "name" : "%(solution_name)s",
846 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000847 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000848 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000849 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000850%(solution_deps)s },
851 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000852 },
853""")
854
855 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
856# Snapshot generated with gclient revinfo --snapshot
857solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000858%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000859""")
860
861 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000862 # Do not change previous behavior. Only solution level and immediate DEPS
863 # are processed.
864 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000865 Dependency.__init__(self, None, None, None, None, True, None, None,
866 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000867 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000868 if options.deps_os:
869 enforced_os = options.deps_os.split(',')
870 else:
871 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
872 if 'all' in enforced_os:
873 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000874 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000875 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000876 self.config_content = None
877
878 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000879 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000880 config_dict = {}
881 self.config_content = content
882 try:
883 exec(content, config_dict)
884 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000885 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000886
peter@chromium.org1efccc82012-04-27 16:34:38 +0000887 # Append any target OS that is not already being enforced to the tuple.
888 target_os = config_dict.get('target_os', [])
889 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
890
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000891 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000892 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000893 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000894 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000895 self, s['name'], s['url'],
896 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000897 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000898 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000899 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000900 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000901 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000902 except KeyError:
903 raise gclient_utils.Error('Invalid .gclient file. Solution is '
904 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000905 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
906 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000907
908 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000909 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000910 self._options.config_filename),
911 self.config_content)
912
913 @staticmethod
914 def LoadCurrentConfig(options):
915 """Searches for and loads a .gclient file relative to the current working
916 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +0000917 if options.spec:
918 client = GClient('.', options)
919 client.SetConfig(options.spec)
920 else:
921 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
922 if not path:
923 return None
924 client = GClient(path, options)
925 client.SetConfig(gclient_utils.FileRead(
926 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000927
928 if (options.revisions and
929 len(client.dependencies) > 1 and
930 any('@' not in r for r in options.revisions)):
931 print >> sys.stderr, (
932 'You must specify the full solution name like --revision %s@%s\n'
933 'when you have multiple solutions setup in your .gclient file.\n'
934 'Other solutions present are: %s.') % (
935 client.dependencies[0].name,
936 options.revisions[0],
937 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000938 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000939
nsylvain@google.comefc80932011-05-31 21:27:56 +0000940 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000941 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000942 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
943 'solution_name': solution_name,
944 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000945 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000946 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000947 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000948 })
949
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000950 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000951 """Creates a .gclient_entries file to record the list of unique checkouts.
952
953 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000954 """
955 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
956 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000957 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000958 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000959 # Skip over File() dependencies as we can't version them.
960 if not isinstance(entry.parsed_url, self.FileImpl):
961 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
962 pprint.pformat(entry.parsed_url))
963 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000964 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000965 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000966 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000967
968 def _ReadEntries(self):
969 """Read the .gclient_entries file for the given client.
970
971 Returns:
972 A sequence of solution names, which will be empty if there is the
973 entries file hasn't been created yet.
974 """
975 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000976 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000977 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000978 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000979 try:
980 exec(gclient_utils.FileRead(filename), scope)
981 except SyntaxError, e:
982 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000983 return scope['entries']
984
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000985 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000986 """Checks for revision overrides."""
987 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000988 if self._options.head:
989 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000990 # Do not check safesync_url if one or more --revision flag is specified.
991 if not self._options.revisions:
992 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000993 if not s.managed:
994 self._options.revisions.append('%s@unmanaged' % s.name)
995 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000996 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000997 if not self._options.revisions:
998 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000999 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001000 index = 0
1001 for revision in self._options.revisions:
1002 if not '@' in revision:
1003 # Support for --revision 123
1004 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001005 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001006 if not sol in solutions_names:
1007 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1008 print >> sys.stderr, ('Please fix your script, having invalid '
1009 '--revision flags will soon considered an error.')
1010 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001011 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001012 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001013 return revision_overrides
1014
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001015 def _ApplySafeSyncRev(self, dep):
1016 """Finds a valid revision from the content of the safesync_url and apply it
1017 by appending revisions to the revision list. Throws if revision appears to
1018 be invalid for the given |dep|."""
1019 assert len(dep.safesync_url) > 0
1020 handle = urllib.urlopen(dep.safesync_url)
1021 rev = handle.read().strip()
1022 handle.close()
1023 if not rev:
1024 raise gclient_utils.Error(
1025 'It appears your safesync_url (%s) is not working properly\n'
1026 '(as it returned an empty response). Check your config.' %
1027 dep.safesync_url)
1028 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
1029 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
1030 if self._options.verbose:
1031 print('Using safesync_url revision: %s.\n' % safe_rev)
1032 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1033
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001034 def RunOnDeps(self, command, args):
1035 """Runs a command on each dependency in a client and its dependencies.
1036
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001037 Args:
1038 command: The command to use (e.g., 'status' or 'diff')
1039 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001041 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001042 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001043 revision_overrides = {}
1044 # It's unnecessary to check for revision overrides for 'recurse'.
1045 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001046 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001047 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001048 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001049 # Disable progress for non-tty stdout.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001050 if (sys.stdout.isatty() and not self._options.verbose):
1051 if command in ('update', 'revert'):
1052 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001053 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001054 pm = Progress(' '.join(args), 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001055 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001056 for s in self.dependencies:
1057 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001058 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001059
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001060 # Once all the dependencies have been processed, it's now safe to run the
1061 # hooks.
1062 if not self._options.nohooks:
1063 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064
1065 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001066 # Notify the user if there is an orphaned entry in their working copy.
1067 # Only delete the directory if there are no changes in it, and
1068 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001069 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001070 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001071 if not prev_url:
1072 # entry must have been overridden via .gclient custom_deps
1073 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001074 # Fix path separator on Windows.
1075 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001076 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001077 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001078 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001079 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001080 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001081 scm.status(self._options, [], file_list)
1082 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001083 if (not self._options.delete_unversioned_trees or
1084 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001085 # There are modified files in this entry. Keep warning until
1086 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001087 print(('\nWARNING: \'%s\' is no longer part of this client. '
1088 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001089 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001090 else:
1091 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001092 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001093 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001094 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001095 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001096 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001097 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001098
1099 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001100 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001101 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001102 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001103 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001104 for s in self.dependencies:
1105 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001106 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001107
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001108 def GetURLAndRev(dep):
1109 """Returns the revision-qualified SCM url for a Dependency."""
1110 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001111 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001112 if isinstance(dep.parsed_url, self.FileImpl):
1113 original_url = dep.parsed_url.file_location
1114 else:
1115 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001116 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001117 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001118 if not os.path.isdir(scm.checkout_path):
1119 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001120 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001121
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001122 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001123 new_gclient = ''
1124 # First level at .gclient
1125 for d in self.dependencies:
1126 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001127 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001128 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001129 for d in dep.dependencies:
1130 entries[d.name] = GetURLAndRev(d)
1131 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001132 GrabDeps(d)
1133 custom_deps = []
1134 for k in sorted(entries.keys()):
1135 if entries[k]:
1136 # Quotes aren't escaped...
1137 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1138 else:
1139 custom_deps.append(' \"%s\": None,\n' % k)
1140 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1141 'solution_name': d.name,
1142 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001143 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001144 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001145 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001146 'solution_deps': ''.join(custom_deps),
1147 }
1148 # Print the snapshot configuration file
1149 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001150 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001151 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001152 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001153 if self._options.actual:
1154 entries[d.name] = GetURLAndRev(d)
1155 else:
1156 entries[d.name] = d.parsed_url
1157 keys = sorted(entries.keys())
1158 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001159 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001160 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001161
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001162 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001163 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001164 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001165
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001166 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001167 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001168 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001169 return self._root_dir
1170
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001171 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001172 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001173 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001174 return self._enforced_os
1175
maruel@chromium.org68988972011-09-20 14:11:42 +00001176 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001177 def recursion_limit(self):
1178 """How recursive can each dependencies in DEPS file can load DEPS file."""
1179 return self._recursion_limit
1180
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001181 @property
1182 def target_os(self):
1183 return self._enforced_os
1184
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001185
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001186#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001187
1188
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001189def CMDcleanup(parser, args):
1190 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001191
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001192Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001193"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001194 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1195 help='override deps for the specified (comma-separated) '
1196 'platform(s); \'all\' will process all deps_os '
1197 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001198 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001199 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001200 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001201 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001202 if options.verbose:
1203 # Print out the .gclient file. This is longer than if we just printed the
1204 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001205 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001206 return client.RunOnDeps('cleanup', args)
1207
1208
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001209@attr('usage', '[command] [args ...]')
1210def CMDrecurse(parser, args):
1211 """Operates on all the entries.
1212
1213 Runs a shell command on all entries.
1214 """
1215 # Stop parsing at the first non-arg so that these go through to the command
1216 parser.disable_interspersed_args()
1217 parser.add_option('-s', '--scm', action='append', default=[],
1218 help='choose scm types to operate upon')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001219 parser.add_option('-i', '--ignore', action='store_true',
1220 help='continue processing in case of non zero return code')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001221 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001222 if not args:
1223 print >> sys.stderr, 'Need to supply a command!'
1224 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001225 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1226 if not root_and_entries:
1227 print >> sys.stderr, (
1228 'You need to run gclient sync at least once to use \'recurse\'.\n'
1229 'This is because .gclient_entries needs to exist and be up to date.')
1230 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001231
1232 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001233 scm_set = set()
1234 for scm in options.scm:
1235 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001236 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001237
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001238 options.nohooks = True
1239 client = GClient.LoadCurrentConfig(options)
1240 return client.RunOnDeps('recurse', args)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001241
1242
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001243@attr('usage', '[args ...]')
1244def CMDfetch(parser, args):
1245 """Fetches upstream commits for all modules.
1246
1247Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1248"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001249 (options, args) = parser.parse_args(args)
1250 args = ['-j%d' % options.jobs, '-s', 'git', 'git', 'fetch'] + args
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001251 return CMDrecurse(parser, args)
1252
1253
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001254@attr('usage', '[url] [safesync url]')
1255def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001256 """Create a .gclient file in the current directory.
1257
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001258This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001259top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001260modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001261provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001262URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001263"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001264
1265 # We do a little dance with the --gclientfile option. 'gclient config' is the
1266 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1267 # arguments. So, we temporarily stash any --gclientfile parameter into
1268 # options.output_config_file until after the (gclientfile xor spec) error
1269 # check.
1270 parser.remove_option('--gclientfile')
1271 parser.add_option('--gclientfile', dest='output_config_file',
1272 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001273 parser.add_option('--name',
1274 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001275 parser.add_option('--deps-file', default='DEPS',
1276 help='overrides the default name for the DEPS file for the'
1277 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001278 parser.add_option('--unmanaged', action='store_true', default=False,
1279 help='overrides the default behavior to make it possible '
1280 'to have the main solution untouched by gclient '
1281 '(gclient will check out unmanaged dependencies but '
1282 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001283 parser.add_option('--git-deps', action='store_true',
1284 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001285 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001286 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001287 if options.output_config_file:
1288 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001289 if ((options.spec and args) or len(args) > 2 or
1290 (not options.spec and not args)):
1291 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1292
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001293 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001294 if options.spec:
1295 client.SetConfig(options.spec)
1296 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001297 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001298 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001299 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001300 if name.endswith('.git'):
1301 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001302 else:
1303 # specify an alternate relpath for the given URL.
1304 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001305 deps_file = options.deps_file
1306 if options.git_deps:
1307 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001308 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001309 if len(args) > 1:
1310 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001311 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1312 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001313 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001314 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001315
1316
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001317@attr('epilog', """Example:
1318 gclient pack > patch.txt
1319 generate simple patch for configured client and dependences
1320""")
1321def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001322 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001323
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001324Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001325dependencies, and performs minimal postprocessing of the output. The
1326resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001327checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001328"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001329 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1330 help='override deps for the specified (comma-separated) '
1331 'platform(s); \'all\' will process all deps_os '
1332 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001333 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001334 (options, args) = parser.parse_args(args)
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001335 # Force jobs to 1 so the stdout is not annotated with the thread ids
1336 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001337 client = GClient.LoadCurrentConfig(options)
1338 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001339 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001340 if options.verbose:
1341 # Print out the .gclient file. This is longer than if we just printed the
1342 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001343 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001344 return client.RunOnDeps('pack', args)
1345
1346
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001347def CMDstatus(parser, args):
1348 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001349 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1350 help='override deps for the specified (comma-separated) '
1351 'platform(s); \'all\' will process all deps_os '
1352 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001353 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001354 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001355 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001356 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001357 if options.verbose:
1358 # Print out the .gclient file. This is longer than if we just printed the
1359 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001360 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001361 return client.RunOnDeps('status', args)
1362
1363
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001364@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001365 gclient sync
1366 update files from SCM according to current configuration,
1367 *for modules which have changed since last update or sync*
1368 gclient sync --force
1369 update files from SCM according to current configuration, for
1370 all modules (useful for recovering files deleted from local copy)
1371 gclient sync --revision src@31000
1372 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001373""")
1374def CMDsync(parser, args):
1375 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001376 parser.add_option('-f', '--force', action='store_true',
1377 help='force update even for unchanged modules')
1378 parser.add_option('-n', '--nohooks', action='store_true',
1379 help='don\'t run hooks after the update is complete')
1380 parser.add_option('-r', '--revision', action='append',
1381 dest='revisions', metavar='REV', default=[],
1382 help='Enforces revision/hash for the solutions with the '
1383 'format src@rev. The src@ part is optional and can be '
1384 'skipped. -r can be used multiple times when .gclient '
1385 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001386 'if the src@ part is skipped. Note that specifying '
1387 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001388 parser.add_option('-t', '--transitive', action='store_true',
1389 help='When a revision is specified (in the DEPS file or '
1390 'with the command-line flag), transitively update '
1391 'the dependencies to the date of the given revision. '
1392 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001393 parser.add_option('-H', '--head', action='store_true',
1394 help='skips any safesync_urls specified in '
1395 'configured solutions and sync to head instead')
1396 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001397 help='Deletes from the working copy any dependencies that '
1398 'have been removed since the last sync, as long as '
1399 'there are no local modifications. When used with '
1400 '--force, such dependencies are removed even if they '
1401 'have local modifications. When used with --reset, '
1402 'all untracked directories are removed from the '
1403 'working copy, exclusing those which are explicitly '
1404 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001405 parser.add_option('-R', '--reset', action='store_true',
1406 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001407 parser.add_option('-M', '--merge', action='store_true',
1408 help='merge upstream changes instead of trying to '
1409 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001410 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1411 help='override deps for the specified (comma-separated) '
1412 'platform(s); \'all\' will process all deps_os '
1413 'references')
1414 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1415 help='Skip svn up whenever possible by requesting '
1416 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001417 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001418 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001419
1420 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001421 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001422
maruel@chromium.org307d1792010-05-31 20:03:13 +00001423 if options.revisions and options.head:
1424 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001425 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001426
1427 if options.verbose:
1428 # Print out the .gclient file. This is longer than if we just printed the
1429 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001430 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001431 return client.RunOnDeps('update', args)
1432
1433
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001434def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001435 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001436 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001437
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001438def CMDdiff(parser, args):
1439 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001440 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1441 help='override deps for the specified (comma-separated) '
1442 'platform(s); \'all\' will process all deps_os '
1443 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001444 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001445 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001446 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001447 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001448 if options.verbose:
1449 # Print out the .gclient file. This is longer than if we just printed the
1450 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001451 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001452 return client.RunOnDeps('diff', args)
1453
1454
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001455def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001456 """Revert all modifications in every dependencies.
1457
1458 That's the nuclear option to get back to a 'clean' state. It removes anything
1459 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001460 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1461 help='override deps for the specified (comma-separated) '
1462 'platform(s); \'all\' will process all deps_os '
1463 'references')
1464 parser.add_option('-n', '--nohooks', action='store_true',
1465 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001466 (options, args) = parser.parse_args(args)
1467 # --force is implied.
1468 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001469 options.reset = False
1470 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001471 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001472 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001473 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001474 return client.RunOnDeps('revert', args)
1475
1476
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001477def CMDrunhooks(parser, args):
1478 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001479 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1480 help='override deps for the specified (comma-separated) '
1481 'platform(s); \'all\' will process all deps_os '
1482 'references')
1483 parser.add_option('-f', '--force', action='store_true', default=True,
1484 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001485 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001486 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001487 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001488 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001489 if options.verbose:
1490 # Print out the .gclient file. This is longer than if we just printed the
1491 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001492 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001493 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001494 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001495 return client.RunOnDeps('runhooks', args)
1496
1497
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001498def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001499 """Output revision info mapping for the client and its dependencies.
1500
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001501 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001502 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001503 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1504 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001505 commit can change.
1506 """
1507 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1508 help='override deps for the specified (comma-separated) '
1509 'platform(s); \'all\' will process all deps_os '
1510 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001511 parser.add_option('-a', '--actual', action='store_true',
1512 help='gets the actual checked out revisions instead of the '
1513 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001514 parser.add_option('-s', '--snapshot', action='store_true',
1515 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001516 'version of all repositories to reproduce the tree, '
1517 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001518 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001519 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001520 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001521 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001522 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001523 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001524
1525
szager@google.comb9a78d32012-03-13 18:46:21 +00001526def CMDhookinfo(parser, args):
1527 """Output the hooks that would be run by `gclient runhooks`"""
1528 (options, args) = parser.parse_args(args)
1529 options.force = True
1530 client = GClient.LoadCurrentConfig(options)
1531 if not client:
1532 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1533 client.RunOnDeps(None, [])
1534 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1535 return 0
1536
1537
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001538def Command(name):
1539 return getattr(sys.modules[__name__], 'CMD' + name, None)
1540
1541
1542def CMDhelp(parser, args):
1543 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001544 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001545 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001546 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001547 parser.print_help()
1548 return 0
1549
1550
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001551def GenUsage(parser, command):
1552 """Modify an OptParse object with the function's documentation."""
1553 obj = Command(command)
1554 if command == 'help':
1555 command = '<command>'
1556 # OptParser.description prefer nicely non-formatted strings.
1557 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1558 usage = getattr(obj, 'usage', '')
1559 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1560 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001561
1562
maruel@chromium.org0895b752011-08-26 20:40:33 +00001563def Parser():
1564 """Returns the default parser."""
1565 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001566 # some arm boards have issues with parallel sync.
1567 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001568 jobs = 1
1569 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001570 jobs = max(8, gclient_utils.NumLocalCpus())
szager@chromium.orge2e03202012-07-31 18:05:16 +00001571 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001572 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001573 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001574 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001575 parser.add_option('-v', '--verbose', action='count', default=0,
1576 help='Produces additional output for diagnostics. Can be '
1577 'used up to three times for more logging info.')
1578 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001579 default=None,
1580 help='Specify an alternate %s file' % gclientfile_default)
1581 parser.add_option('--spec',
1582 default=None,
1583 help='create a gclient file containing the provided '
1584 'string. Due to Cygwin/Python brokenness, it '
1585 'probably can\'t contain any newlines.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001586 # Integrate standard options processing.
1587 old_parser = parser.parse_args
1588 def Parse(args):
1589 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001590 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1591 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001592 logging.basicConfig(level=level,
1593 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001594 if options.config_filename and options.spec:
1595 parser.error('Cannot specifiy both --gclientfile and --spec')
1596 if not options.config_filename:
1597 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001598 options.entries_filename = options.config_filename + '_entries'
1599 if options.jobs < 1:
1600 parser.error('--jobs must be 1 or higher')
1601
1602 # These hacks need to die.
1603 if not hasattr(options, 'revisions'):
1604 # GClient.RunOnDeps expects it even if not applicable.
1605 options.revisions = []
1606 if not hasattr(options, 'head'):
1607 options.head = None
1608 if not hasattr(options, 'nohooks'):
1609 options.nohooks = True
1610 if not hasattr(options, 'deps_os'):
1611 options.deps_os = None
1612 if not hasattr(options, 'manually_grab_svn_rev'):
1613 options.manually_grab_svn_rev = None
1614 if not hasattr(options, 'force'):
1615 options.force = None
1616 return (options, args)
1617 parser.parse_args = Parse
1618 # We don't want wordwrapping in epilog (usually examples)
1619 parser.format_epilog = lambda _: parser.epilog or ''
1620 return parser
1621
1622
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001623def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001624 """Doesn't parse the arguments here, just find the right subcommand to
1625 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001626 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001627 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001628 '\nYour python version %s is unsupported, please upgrade.\n' %
1629 sys.version.split(' ', 1)[0])
1630 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001631 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001632 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001633 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1634 # operations. Python as a strong tendency to buffer sys.stdout.
1635 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001636 # Make stdout annotated with the thread ids.
1637 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001638 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001639 # Unused variable 'usage'
1640 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001641 def to_str(fn):
1642 return (
1643 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1644 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1645 cmds = (
1646 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1647 )
1648 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001649 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001650 if argv:
1651 command = Command(argv[0])
1652 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001653 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001654 GenUsage(parser, argv[0])
1655 return command(parser, argv[1:])
1656 # Not a known command. Default to help.
1657 GenUsage(parser, 'help')
1658 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001659 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001660 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001661 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001662
1663
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001664if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001665 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001666 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001667
1668# vim: ts=2:sw=2:tw=80:et: