blob: ab52cc8eee6098eed83c4ce41dd652606ebf4bea [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
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000175
176 # These are only set in .gclient and not in DEPS files.
177 self._custom_vars = custom_vars or {}
178 self._custom_deps = custom_deps or {}
179
maruel@chromium.org064186c2011-09-27 23:53:33 +0000180 # Post process the url to remove trailing slashes.
181 if isinstance(self._url, basestring):
182 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
183 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000184 self._url = self._url.replace('/@', '@')
185 elif not isinstance(self._url,
186 (self.FromImpl, self.FileImpl, None.__class__)):
187 raise gclient_utils.Error(
188 ('dependency url must be either a string, None, '
189 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000190 if '/' in self._deps_file or '\\' in self._deps_file:
191 raise gclient_utils.Error('deps_file name must not be a path, just a '
192 'filename. %s' % self._deps_file)
193
194 @property
195 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000196 return self._deps_file
197
198 @property
199 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000200 return self._managed
201
202 @property
203 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000204 return self._parent
205
206 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000207 def root(self):
208 """Returns the root node, a GClient object."""
209 if not self.parent:
210 # This line is to signal pylint that it could be a GClient instance.
211 return self or GClient(None, None)
212 return self.parent.root
213
214 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000215 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000216 return self._safesync_url
217
218 @property
219 def should_process(self):
220 """True if this dependency should be processed, i.e. checked out."""
221 return self._should_process
222
223 @property
224 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 return self._custom_vars.copy()
226
227 @property
228 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000229 return self._custom_deps.copy()
230
maruel@chromium.org064186c2011-09-27 23:53:33 +0000231 @property
232 def url(self):
233 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000234
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000235 @property
236 def recursion_limit(self):
237 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000238 if self.recursion_override is not None:
239 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000240 return max(self.parent.recursion_limit - 1, 0)
241
242 def get_custom_deps(self, name, url):
243 """Returns a custom deps if applicable."""
244 if self.parent:
245 url = self.parent.get_custom_deps(name, url)
246 # None is a valid return value to disable a dependency.
247 return self.custom_deps.get(name, url)
248
maruel@chromium.org064186c2011-09-27 23:53:33 +0000249
250class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000251 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000252
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000253 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000254 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000255 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000256 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000258 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000259
260 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000261 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000262
263 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000264 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000265 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000266 # A cache of the files affected by the current operation, necessary for
267 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000268 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000269 # If it is not set to True, the dependency wasn't processed for its child
270 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000271 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000272 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000273 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000274 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000275 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000276
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000277 if not self.name and self.parent:
278 raise gclient_utils.Error('Dependency without name')
279
maruel@chromium.org470b5432011-10-11 18:18:19 +0000280 @property
281 def requirements(self):
282 """Calculate the list of requirements."""
283 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000284 # self.parent is implicitly a requirement. This will be recursive by
285 # definition.
286 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000287 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000288
289 # For a tree with at least 2 levels*, the leaf node needs to depend
290 # on the level higher up in an orderly way.
291 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
292 # thus unsorted, while the .gclient format is a list thus sorted.
293 #
294 # * _recursion_limit is hard coded 2 and there is no hope to change this
295 # value.
296 #
297 # Interestingly enough, the following condition only works in the case we
298 # want: self is a 2nd level node. 3nd level node wouldn't need this since
299 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000300 if self.parent and self.parent.parent and not self.parent.parent.parent:
301 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000302
303 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000304 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000305
maruel@chromium.org470b5432011-10-11 18:18:19 +0000306 if self.name:
307 requirements |= set(
308 obj.name for obj in self.root.subtree(False)
309 if (obj is not self
310 and obj.name and
311 self.name.startswith(posixpath.join(obj.name, ''))))
312 requirements = tuple(sorted(requirements))
313 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
314 return requirements
315
316 def verify_validity(self):
317 """Verifies that this Dependency is fine to add as a child of another one.
318
319 Returns True if this entry should be added, False if it is a duplicate of
320 another entry.
321 """
322 logging.info('Dependency(%s).verify_validity()' % self.name)
323 if self.name in [s.name for s in self.parent.dependencies]:
324 raise gclient_utils.Error(
325 'The same name "%s" appears multiple times in the deps section' %
326 self.name)
327 if not self.should_process:
328 # Return early, no need to set requirements.
329 return True
330
331 # This require a full tree traversal with locks.
332 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
333 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000334 self_url = self.LateOverride(self.url)
335 sibling_url = sibling.LateOverride(sibling.url)
336 # Allow to have only one to be None or ''.
337 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000338 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000339 ('Dependency %s specified more than once:\n'
340 ' %s [%s]\n'
341 'vs\n'
342 ' %s [%s]') % (
343 self.name,
344 sibling.hierarchy(),
345 sibling_url,
346 self.hierarchy(),
347 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000348 # In theory we could keep it as a shadow of the other one. In
349 # practice, simply ignore it.
350 logging.warn('Won\'t process duplicate dependency %s' % sibling)
351 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000352 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000353
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000354 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000355 """Resolves the parsed url from url.
356
357 Manages From() keyword accordingly. Do not touch self.parsed_url nor
358 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000359 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000360 parsed_url = self.get_custom_deps(self.name, url)
361 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000362 logging.info(
363 'Dependency(%s).LateOverride(%s) -> %s' %
364 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000365 return parsed_url
366
367 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000368 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000369 ref = [
370 dep for dep in self.root.subtree(True) if url.module_name == dep.name
371 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000372 if not ref:
373 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
374 url.module_name, ref))
375 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000376 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000377 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000378 found_deps = [d for d in ref.dependencies if d.name == sub_target]
379 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000380 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000381 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
382 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000383 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000384
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000385 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000386 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000387 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000388 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000389 'Dependency(%s).LateOverride(%s) -> %s (From)' %
390 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000391 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000392
393 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000394 parsed_url = urlparse.urlparse(url)
395 if not parsed_url[0]:
396 # A relative url. Fetch the real base.
397 path = parsed_url[2]
398 if not path.startswith('/'):
399 raise gclient_utils.Error(
400 'relative DEPS entry \'%s\' must begin with a slash' % url)
401 # Create a scm just to query the full url.
402 parent_url = self.parent.parsed_url
403 if isinstance(parent_url, self.FileImpl):
404 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000405 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000406 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000407 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000408 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000409 logging.info(
410 'Dependency(%s).LateOverride(%s) -> %s' %
411 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000412 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000413
414 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000415 logging.info(
416 'Dependency(%s).LateOverride(%s) -> %s (File)' %
417 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000418 return url
419
420 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000421 logging.info(
422 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000423 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000424
425 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000426
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000427 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000428 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000429 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000430 assert not self.dependencies
431 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000432 local_scope = {}
433 var = self.VarImpl(self.custom_vars, local_scope)
434 global_scope = {
435 'File': self.FileImpl,
436 'From': self.FromImpl,
437 'Var': var.Lookup,
438 'deps_os': {},
439 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000440 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000441 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000442 logging.info(
443 'ParseDepsFile(%s): No %s file found at %s' % (
444 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000445 else:
446 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000447 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000448 # Eval the content.
449 try:
450 exec(deps_content, global_scope, local_scope)
451 except SyntaxError, e:
452 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000453 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000454 if 'recursion' in local_scope:
455 self.recursion_override = local_scope.get('recursion')
456 logging.warning(
457 'Setting %s recursion to %d.', self.name, self.recursion_limit)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000458 # load os specific dependencies if defined. these dependencies may
459 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000460 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000461 enforced_os = self.root.enforced_os
462 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000463 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000464 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000465 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000466 # platform, so we collect the broadest set of dependencies
467 # available. We may end up with the wrong revision of something for
468 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000469 deps.update([x for x in os_deps.items() if not x[0] in deps])
470 else:
471 deps.update(os_deps)
472
maruel@chromium.org271375b2010-06-23 19:17:38 +0000473 # If a line is in custom_deps, but not in the solution, we want to append
474 # this line to the solution.
475 for d in self.custom_deps:
476 if d not in deps:
477 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000478
479 # If use_relative_paths is set in the DEPS file, regenerate
480 # the dictionary using paths relative to the directory containing
481 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000482 use_relative_paths = local_scope.get('use_relative_paths', False)
483 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000484 rel_deps = {}
485 for d, url in deps.items():
486 # normpath is required to allow DEPS to use .. in their
487 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000488 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
489 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000490
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000491 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000492 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000493 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000494 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000495 deps_to_add.append(Dependency(
496 self, name, url, None, None, None, None,
497 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000498 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000499 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
500 logging.info('ParseDepsFile(%s) done' % self.name)
501
502 def add_dependencies_and_close(self, deps_to_add, hooks):
503 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000504 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000505 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000506 self.add_dependency(dep)
507 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000508
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000509 @staticmethod
510 def maybeGetParentRevision(
511 command, options, parsed_url, parent_name, revision_overrides):
512 """If we are performing an update and --transitive is set, set the
513 revision to the parent's revision. If we have an explicit revision
514 do nothing."""
515 if command == 'update' and options.transitive and not options.revision:
516 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
517 if not revision:
518 options.revision = revision_overrides.get(parent_name)
519 if options.verbose and options.revision:
520 print("Using parent's revision date: %s" % options.revision)
521 # If the parent has a revision override, then it must have been
522 # converted to date format.
523 assert (not options.revision or
524 gclient_utils.IsDateRevision(options.revision))
525
526 @staticmethod
527 def maybeConvertToDateRevision(
528 command, options, name, scm, revision_overrides):
529 """If we are performing an update and --transitive is set, convert the
530 revision to a date-revision (if necessary). Instead of having
531 -r 101 replace the revision with the time stamp of 101 (e.g.
532 "{2011-18-04}").
533 This way dependencies are upgraded to the revision they had at the
534 check-in of revision 101."""
535 if (command == 'update' and
536 options.transitive and
537 options.revision and
538 not gclient_utils.IsDateRevision(options.revision)):
539 revision_date = scm.GetRevisionDate(options.revision)
540 revision = gclient_utils.MakeDateRevision(revision_date)
541 if options.verbose:
542 print("Updating revision override from %s to %s." %
543 (options.revision, revision))
544 revision_overrides[name] = revision
545
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000546 # Arguments number differs from overridden method
547 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000548 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000549 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000550 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000551 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000552 if not self.should_process:
553 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000554 # When running runhooks, there's no need to consult the SCM.
555 # All known hooks are expected to run unconditionally regardless of working
556 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000557 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000558 parsed_url = self.LateOverride(self.url)
559 file_list = []
560 if run_scm and parsed_url:
561 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000562 # Special support for single-file checkout.
563 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000564 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
565 # pylint: disable=E1103
566 options.revision = parsed_url.GetRevision()
567 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000568 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000569 self.name)
570 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000571 args + [parsed_url.GetFilename()],
572 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000573 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000574 # Create a shallow copy to mutate revision.
575 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000576 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000577 self.maybeGetParentRevision(
578 command, options, parsed_url, self.parent.name, revision_overrides)
579 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
580 scm.RunCommand(command, options, args, file_list)
581 self.maybeConvertToDateRevision(
582 command, options, self.name, scm, revision_overrides)
583 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000584
585 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
586 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000587 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000588 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000589 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000590 continue
591 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000592 [self.root.root_dir.lower(), file_list[i].lower()])
593 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000594 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000595 while file_list[i].startswith(('\\', '/')):
596 file_list[i] = file_list[i][1:]
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +0000597 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000598 if not isinstance(parsed_url, self.FileImpl):
599 # Skip file only checkout.
600 scm = gclient_scm.GetScmName(parsed_url)
601 if not options.scm or scm in options.scm:
602 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
603 # Pass in the SCM type as an env variable
604 env = os.environ.copy()
605 if scm:
606 env['GCLIENT_SCM'] = scm
607 if parsed_url:
608 env['GCLIENT_URL'] = parsed_url
609 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000610 try:
611 gclient_utils.CheckCallAndFilter(
612 args, cwd=cwd, env=env, print_stdout=True)
613 except subprocess2.CalledProcessError:
614 if not options.ignore:
615 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000616 else:
617 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000618
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000619 # Always parse the DEPS file.
620 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000621
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000622 self._run_is_done(file_list, parsed_url)
623
624 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000625 # Parse the dependencies of this dependency.
626 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000627 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000628
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000629 @gclient_utils.lockedmethod
630 def _run_is_done(self, file_list, parsed_url):
631 # Both these are kept for hooks that are run as a separate tree traversal.
632 self._file_list = file_list
633 self._parsed_url = parsed_url
634 self._processed = True
635
szager@google.comb9a78d32012-03-13 18:46:21 +0000636 @staticmethod
637 def GetHookAction(hook_dict, matching_file_list):
638 """Turns a parsed 'hook' dict into an executable command."""
639 logging.debug(hook_dict)
640 logging.debug(matching_file_list)
641 command = hook_dict['action'][:]
642 if command[0] == 'python':
643 # If the hook specified "python" as the first item, the action is a
644 # Python script. Run it by starting a new copy of the same
645 # interpreter.
646 command[0] = sys.executable
647 if '$matching_files' in command:
648 splice_index = command.index('$matching_files')
649 command[splice_index:splice_index + 1] = matching_file_list
650 return command
651
652 def GetHooks(self, options):
653 """Evaluates all hooks, and return them in a flat list.
654
655 RunOnDeps() must have been called before to load the DEPS.
656 """
657 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000658 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000659 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000660 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000661 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000662 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000663 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000664 # TODO(maruel): If the user is using git or git-svn, then we don't know
665 # what files have changed so we always run all hooks. It'd be nice to fix
666 # that.
667 if (options.force or
668 isinstance(self.parsed_url, self.FileImpl) or
669 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000670 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000671 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000672 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000673 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000674 # Run hooks on the basis of whether the files from the gclient operation
675 # match each hook's pattern.
676 for hook_dict in self.deps_hooks:
677 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000678 matching_file_list = [
679 f for f in self.file_list_and_children if pattern.search(f)
680 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000681 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000682 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000683 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000684 result.extend(s.GetHooks(options))
685 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000686
szager@google.comb9a78d32012-03-13 18:46:21 +0000687 def RunHooksRecursively(self, options):
688 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000689 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000690 for hook in self.GetHooks(options):
691 try:
692 gclient_utils.CheckCallAndFilterAndHeader(
693 hook, cwd=self.root.root_dir, always=True)
694 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
695 # Use a discrete exit status code of 2 to indicate that a hook action
696 # failed. Users of this script may wish to treat hook action failures
697 # differently from VC failures.
698 print >> sys.stderr, 'Error: %s' % str(e)
699 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000700
maruel@chromium.org0d812442010-08-10 12:41:08 +0000701 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000702 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000703 dependencies = self.dependencies
704 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000705 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000706 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000707 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000708 for i in d.subtree(include_all):
709 yield i
710
711 def depth_first_tree(self):
712 """Depth-first recursion including the root node."""
713 yield self
714 for i in self.dependencies:
715 for j in i.depth_first_tree():
716 if j.should_process:
717 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000718
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000719 @gclient_utils.lockedmethod
720 def add_dependency(self, new_dep):
721 self._dependencies.append(new_dep)
722
723 @gclient_utils.lockedmethod
724 def _mark_as_parsed(self, new_hooks):
725 self._deps_hooks.extend(new_hooks)
726 self._deps_parsed = True
727
maruel@chromium.org68988972011-09-20 14:11:42 +0000728 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000729 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000730 def dependencies(self):
731 return tuple(self._dependencies)
732
733 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000734 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000735 def deps_hooks(self):
736 return tuple(self._deps_hooks)
737
738 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000739 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000740 def parsed_url(self):
741 return self._parsed_url
742
743 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000744 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000745 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000746 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000747 return self._deps_parsed
748
749 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000750 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000751 def processed(self):
752 return self._processed
753
754 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000755 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000756 def hooks_ran(self):
757 return self._hooks_ran
758
759 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000760 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000761 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000762 return tuple(self._file_list)
763
764 @property
765 def file_list_and_children(self):
766 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000767 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000768 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000769 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000770
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000771 def __str__(self):
772 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000773 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000774 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000775 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000776 # First try the native property if it exists.
777 if hasattr(self, '_' + i):
778 value = getattr(self, '_' + i, False)
779 else:
780 value = getattr(self, i, False)
781 if value:
782 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000783
784 for d in self.dependencies:
785 out.extend([' ' + x for x in str(d).splitlines()])
786 out.append('')
787 return '\n'.join(out)
788
789 def __repr__(self):
790 return '%s: %s' % (self.name, self.url)
791
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000792 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000793 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000794 out = '%s(%s)' % (self.name, self.url)
795 i = self.parent
796 while i and i.name:
797 out = '%s(%s) -> %s' % (i.name, i.url, out)
798 i = i.parent
799 return out
800
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000801
802class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000803 """Object that represent a gclient checkout. A tree of Dependency(), one per
804 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000805
806 DEPS_OS_CHOICES = {
807 "win32": "win",
808 "win": "win",
809 "cygwin": "win",
810 "darwin": "mac",
811 "mac": "mac",
812 "unix": "unix",
813 "linux": "unix",
814 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000815 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000816 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000817 }
818
819 DEFAULT_CLIENT_FILE_TEXT = ("""\
820solutions = [
821 { "name" : "%(solution_name)s",
822 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000823 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000824 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000825 "custom_deps" : {
826 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000827 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000828 },
829]
830""")
831
832 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
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" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000838%(solution_deps)s },
839 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000840 },
841""")
842
843 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
844# Snapshot generated with gclient revinfo --snapshot
845solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000846%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000847""")
848
849 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000850 # Do not change previous behavior. Only solution level and immediate DEPS
851 # are processed.
852 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000853 Dependency.__init__(self, None, None, None, None, True, None, None,
854 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000855 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000856 if options.deps_os:
857 enforced_os = options.deps_os.split(',')
858 else:
859 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
860 if 'all' in enforced_os:
861 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000862 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000863 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000864 self.config_content = None
865
866 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000867 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000868 config_dict = {}
869 self.config_content = content
870 try:
871 exec(content, config_dict)
872 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000873 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000874
peter@chromium.org1efccc82012-04-27 16:34:38 +0000875 # Append any target OS that is not already being enforced to the tuple.
876 target_os = config_dict.get('target_os', [])
877 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
878
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000879 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000880 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000881 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000882 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000883 self, s['name'], s['url'],
884 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000885 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000886 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000887 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000888 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000889 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000890 except KeyError:
891 raise gclient_utils.Error('Invalid .gclient file. Solution is '
892 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000893 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
894 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000895
896 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000897 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000898 self._options.config_filename),
899 self.config_content)
900
901 @staticmethod
902 def LoadCurrentConfig(options):
903 """Searches for and loads a .gclient file relative to the current working
904 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +0000905 if options.spec:
906 client = GClient('.', options)
907 client.SetConfig(options.spec)
908 else:
909 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
910 if not path:
911 return None
912 client = GClient(path, options)
913 client.SetConfig(gclient_utils.FileRead(
914 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000915
916 if (options.revisions and
917 len(client.dependencies) > 1 and
918 any('@' not in r for r in options.revisions)):
919 print >> sys.stderr, (
920 'You must specify the full solution name like --revision %s@%s\n'
921 'when you have multiple solutions setup in your .gclient file.\n'
922 'Other solutions present are: %s.') % (
923 client.dependencies[0].name,
924 options.revisions[0],
925 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000926 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000927
nsylvain@google.comefc80932011-05-31 21:27:56 +0000928 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000929 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000930 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
931 'solution_name': solution_name,
932 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000933 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000934 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000935 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000936 })
937
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000938 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000939 """Creates a .gclient_entries file to record the list of unique checkouts.
940
941 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000942 """
943 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
944 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000945 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000946 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000947 # Skip over File() dependencies as we can't version them.
948 if not isinstance(entry.parsed_url, self.FileImpl):
949 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
950 pprint.pformat(entry.parsed_url))
951 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000952 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000953 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000954 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000955
956 def _ReadEntries(self):
957 """Read the .gclient_entries file for the given client.
958
959 Returns:
960 A sequence of solution names, which will be empty if there is the
961 entries file hasn't been created yet.
962 """
963 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000964 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000965 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000966 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000967 try:
968 exec(gclient_utils.FileRead(filename), scope)
969 except SyntaxError, e:
970 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000971 return scope['entries']
972
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000973 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000974 """Checks for revision overrides."""
975 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000976 if self._options.head:
977 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000978 # Do not check safesync_url if one or more --revision flag is specified.
979 if not self._options.revisions:
980 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000981 if not s.managed:
982 self._options.revisions.append('%s@unmanaged' % s.name)
983 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000984 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000985 if not self._options.revisions:
986 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000987 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000988 index = 0
989 for revision in self._options.revisions:
990 if not '@' in revision:
991 # Support for --revision 123
992 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000993 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000994 if not sol in solutions_names:
995 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
996 print >> sys.stderr, ('Please fix your script, having invalid '
997 '--revision flags will soon considered an error.')
998 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000999 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001000 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001001 return revision_overrides
1002
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001003 def _ApplySafeSyncRev(self, dep):
1004 """Finds a valid revision from the content of the safesync_url and apply it
1005 by appending revisions to the revision list. Throws if revision appears to
1006 be invalid for the given |dep|."""
1007 assert len(dep.safesync_url) > 0
1008 handle = urllib.urlopen(dep.safesync_url)
1009 rev = handle.read().strip()
1010 handle.close()
1011 if not rev:
1012 raise gclient_utils.Error(
1013 'It appears your safesync_url (%s) is not working properly\n'
1014 '(as it returned an empty response). Check your config.' %
1015 dep.safesync_url)
1016 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
1017 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
1018 if self._options.verbose:
1019 print('Using safesync_url revision: %s.\n' % safe_rev)
1020 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1021
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001022 def RunOnDeps(self, command, args):
1023 """Runs a command on each dependency in a client and its dependencies.
1024
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001025 Args:
1026 command: The command to use (e.g., 'status' or 'diff')
1027 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001028 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001029 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001030 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001031 revision_overrides = {}
1032 # It's unnecessary to check for revision overrides for 'recurse'.
1033 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001034 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001035 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001036 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001037 # Disable progress for non-tty stdout.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001038 if (sys.stdout.isatty() and not self._options.verbose):
1039 if command in ('update', 'revert'):
1040 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001041 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001042 pm = Progress(' '.join(args), 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001043 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001044 for s in self.dependencies:
1045 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001046 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001047
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001048 # Once all the dependencies have been processed, it's now safe to run the
1049 # hooks.
1050 if not self._options.nohooks:
1051 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001052
1053 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001054 # Notify the user if there is an orphaned entry in their working copy.
1055 # Only delete the directory if there are no changes in it, and
1056 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001057 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001058 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001059 if not prev_url:
1060 # entry must have been overridden via .gclient custom_deps
1061 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001062 # Fix path separator on Windows.
1063 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001064 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001065 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001066 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001067 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001068 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001069 scm.status(self._options, [], file_list)
1070 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001071 if (not self._options.delete_unversioned_trees or
1072 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001073 # There are modified files in this entry. Keep warning until
1074 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001075 print(('\nWARNING: \'%s\' is no longer part of this client. '
1076 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001077 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001078 else:
1079 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001080 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001081 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001082 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001083 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001084 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001085 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001086
1087 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001088 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001089 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001090 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001091 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001092 for s in self.dependencies:
1093 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001094 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001095
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001096 def GetURLAndRev(dep):
1097 """Returns the revision-qualified SCM url for a Dependency."""
1098 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001099 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001100 if isinstance(dep.parsed_url, self.FileImpl):
1101 original_url = dep.parsed_url.file_location
1102 else:
1103 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001104 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001105 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001106 if not os.path.isdir(scm.checkout_path):
1107 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001108 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001109
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001110 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001111 new_gclient = ''
1112 # First level at .gclient
1113 for d in self.dependencies:
1114 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001115 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001116 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001117 for d in dep.dependencies:
1118 entries[d.name] = GetURLAndRev(d)
1119 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001120 GrabDeps(d)
1121 custom_deps = []
1122 for k in sorted(entries.keys()):
1123 if entries[k]:
1124 # Quotes aren't escaped...
1125 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1126 else:
1127 custom_deps.append(' \"%s\": None,\n' % k)
1128 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1129 'solution_name': d.name,
1130 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001131 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001132 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001133 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001134 'solution_deps': ''.join(custom_deps),
1135 }
1136 # Print the snapshot configuration file
1137 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001138 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001139 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001140 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001141 if self._options.actual:
1142 entries[d.name] = GetURLAndRev(d)
1143 else:
1144 entries[d.name] = d.parsed_url
1145 keys = sorted(entries.keys())
1146 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001147 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001148 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001149
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001150 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001151 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001152 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001153
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001154 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001155 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001156 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001157 return self._root_dir
1158
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001159 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001160 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001161 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001162 return self._enforced_os
1163
maruel@chromium.org68988972011-09-20 14:11:42 +00001164 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001165 def recursion_limit(self):
1166 """How recursive can each dependencies in DEPS file can load DEPS file."""
1167 return self._recursion_limit
1168
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001169
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001170#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001171
1172
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001173def CMDcleanup(parser, args):
1174 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001175
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001176Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001177"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001178 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1179 help='override deps for the specified (comma-separated) '
1180 'platform(s); \'all\' will process all deps_os '
1181 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001182 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001183 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001184 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001185 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001186 if options.verbose:
1187 # Print out the .gclient file. This is longer than if we just printed the
1188 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001189 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001190 return client.RunOnDeps('cleanup', args)
1191
1192
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001193@attr('usage', '[command] [args ...]')
1194def CMDrecurse(parser, args):
1195 """Operates on all the entries.
1196
1197 Runs a shell command on all entries.
1198 """
1199 # Stop parsing at the first non-arg so that these go through to the command
1200 parser.disable_interspersed_args()
1201 parser.add_option('-s', '--scm', action='append', default=[],
1202 help='choose scm types to operate upon')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001203 parser.add_option('-i', '--ignore', action='store_true',
1204 help='continue processing in case of non zero return code')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001205 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001206 if not args:
1207 print >> sys.stderr, 'Need to supply a command!'
1208 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001209 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1210 if not root_and_entries:
1211 print >> sys.stderr, (
1212 'You need to run gclient sync at least once to use \'recurse\'.\n'
1213 'This is because .gclient_entries needs to exist and be up to date.')
1214 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001215
1216 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001217 scm_set = set()
1218 for scm in options.scm:
1219 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001220 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001221
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001222 options.nohooks = True
1223 client = GClient.LoadCurrentConfig(options)
1224 return client.RunOnDeps('recurse', args)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001225
1226
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001227@attr('usage', '[args ...]')
1228def CMDfetch(parser, args):
1229 """Fetches upstream commits for all modules.
1230
1231Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1232"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001233 (options, args) = parser.parse_args(args)
1234 args = ['-j%d' % options.jobs, '-s', 'git', 'git', 'fetch'] + args
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001235 return CMDrecurse(parser, args)
1236
1237
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001238@attr('usage', '[url] [safesync url]')
1239def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001240 """Create a .gclient file in the current directory.
1241
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001242This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001243top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001244modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001245provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001246URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001247"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001248
1249 # We do a little dance with the --gclientfile option. 'gclient config' is the
1250 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1251 # arguments. So, we temporarily stash any --gclientfile parameter into
1252 # options.output_config_file until after the (gclientfile xor spec) error
1253 # check.
1254 parser.remove_option('--gclientfile')
1255 parser.add_option('--gclientfile', dest='output_config_file',
1256 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001257 parser.add_option('--name',
1258 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001259 parser.add_option('--deps-file', default='DEPS',
1260 help='overrides the default name for the DEPS file for the'
1261 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001262 parser.add_option('--unmanaged', action='store_true', default=False,
1263 help='overrides the default behavior to make it possible '
1264 'to have the main solution untouched by gclient '
1265 '(gclient will check out unmanaged dependencies but '
1266 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001267 parser.add_option('--git-deps', action='store_true',
1268 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001269 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001270 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001271 if options.output_config_file:
1272 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001273 if ((options.spec and args) or len(args) > 2 or
1274 (not options.spec and not args)):
1275 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1276
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001277 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001278 if options.spec:
1279 client.SetConfig(options.spec)
1280 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001281 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001282 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001283 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001284 if name.endswith('.git'):
1285 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001286 else:
1287 # specify an alternate relpath for the given URL.
1288 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001289 deps_file = options.deps_file
1290 if options.git_deps:
1291 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001292 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001293 if len(args) > 1:
1294 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001295 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1296 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001297 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001298 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001299
1300
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001301@attr('epilog', """Example:
1302 gclient pack > patch.txt
1303 generate simple patch for configured client and dependences
1304""")
1305def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001306 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001307
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001308Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001309dependencies, and performs minimal postprocessing of the output. The
1310resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001311checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001312"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001313 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1314 help='override deps for the specified (comma-separated) '
1315 'platform(s); \'all\' will process all deps_os '
1316 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001317 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001318 (options, args) = parser.parse_args(args)
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001319 # Force jobs to 1 so the stdout is not annotated with the thread ids
1320 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001321 client = GClient.LoadCurrentConfig(options)
1322 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001323 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001324 if options.verbose:
1325 # Print out the .gclient file. This is longer than if we just printed the
1326 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001327 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001328 return client.RunOnDeps('pack', args)
1329
1330
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001331def CMDstatus(parser, args):
1332 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001333 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1334 help='override deps for the specified (comma-separated) '
1335 'platform(s); \'all\' will process all deps_os '
1336 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001337 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001338 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001339 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001340 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001341 if options.verbose:
1342 # Print out the .gclient file. This is longer than if we just printed the
1343 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001344 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001345 return client.RunOnDeps('status', args)
1346
1347
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001348@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001349 gclient sync
1350 update files from SCM according to current configuration,
1351 *for modules which have changed since last update or sync*
1352 gclient sync --force
1353 update files from SCM according to current configuration, for
1354 all modules (useful for recovering files deleted from local copy)
1355 gclient sync --revision src@31000
1356 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001357""")
1358def CMDsync(parser, args):
1359 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001360 parser.add_option('-f', '--force', action='store_true',
1361 help='force update even for unchanged modules')
1362 parser.add_option('-n', '--nohooks', action='store_true',
1363 help='don\'t run hooks after the update is complete')
1364 parser.add_option('-r', '--revision', action='append',
1365 dest='revisions', metavar='REV', default=[],
1366 help='Enforces revision/hash for the solutions with the '
1367 'format src@rev. The src@ part is optional and can be '
1368 'skipped. -r can be used multiple times when .gclient '
1369 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001370 'if the src@ part is skipped. Note that specifying '
1371 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001372 parser.add_option('-t', '--transitive', action='store_true',
1373 help='When a revision is specified (in the DEPS file or '
1374 'with the command-line flag), transitively update '
1375 'the dependencies to the date of the given revision. '
1376 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001377 parser.add_option('-H', '--head', action='store_true',
1378 help='skips any safesync_urls specified in '
1379 'configured solutions and sync to head instead')
1380 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001381 help='Deletes from the working copy any dependencies that '
1382 'have been removed since the last sync, as long as '
1383 'there are no local modifications. When used with '
1384 '--force, such dependencies are removed even if they '
1385 'have local modifications. When used with --reset, '
1386 'all untracked directories are removed from the '
1387 'working copy, exclusing those which are explicitly '
1388 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001389 parser.add_option('-R', '--reset', action='store_true',
1390 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001391 parser.add_option('-M', '--merge', action='store_true',
1392 help='merge upstream changes instead of trying to '
1393 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001394 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1395 help='override deps for the specified (comma-separated) '
1396 'platform(s); \'all\' will process all deps_os '
1397 'references')
1398 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1399 help='Skip svn up whenever possible by requesting '
1400 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001401 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001402 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001403
1404 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001405 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001406
maruel@chromium.org307d1792010-05-31 20:03:13 +00001407 if options.revisions and options.head:
1408 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001409 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001410
1411 if options.verbose:
1412 # Print out the .gclient file. This is longer than if we just printed the
1413 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001414 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001415 return client.RunOnDeps('update', args)
1416
1417
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001418def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001419 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001420 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001421
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001422def CMDdiff(parser, args):
1423 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001424 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1425 help='override deps for the specified (comma-separated) '
1426 'platform(s); \'all\' will process all deps_os '
1427 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001428 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001429 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001430 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001431 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001432 if options.verbose:
1433 # Print out the .gclient file. This is longer than if we just printed the
1434 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001435 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001436 return client.RunOnDeps('diff', args)
1437
1438
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001439def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001440 """Revert all modifications in every dependencies.
1441
1442 That's the nuclear option to get back to a 'clean' state. It removes anything
1443 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001444 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1445 help='override deps for the specified (comma-separated) '
1446 'platform(s); \'all\' will process all deps_os '
1447 'references')
1448 parser.add_option('-n', '--nohooks', action='store_true',
1449 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001450 (options, args) = parser.parse_args(args)
1451 # --force is implied.
1452 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001453 options.reset = False
1454 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001455 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001457 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001458 return client.RunOnDeps('revert', args)
1459
1460
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001461def CMDrunhooks(parser, args):
1462 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001463 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1464 help='override deps for the specified (comma-separated) '
1465 'platform(s); \'all\' will process all deps_os '
1466 'references')
1467 parser.add_option('-f', '--force', action='store_true', default=True,
1468 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001469 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001470 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001471 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001472 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001473 if options.verbose:
1474 # Print out the .gclient file. This is longer than if we just printed the
1475 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001476 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001477 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001478 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001479 return client.RunOnDeps('runhooks', args)
1480
1481
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001482def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001483 """Output revision info mapping for the client and its dependencies.
1484
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001485 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001486 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001487 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1488 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001489 commit can change.
1490 """
1491 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1492 help='override deps for the specified (comma-separated) '
1493 'platform(s); \'all\' will process all deps_os '
1494 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001495 parser.add_option('-a', '--actual', action='store_true',
1496 help='gets the actual checked out revisions instead of the '
1497 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001498 parser.add_option('-s', '--snapshot', action='store_true',
1499 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001500 'version of all repositories to reproduce the tree, '
1501 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001502 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001503 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001504 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001505 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001506 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001507 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001508
1509
szager@google.comb9a78d32012-03-13 18:46:21 +00001510def CMDhookinfo(parser, args):
1511 """Output the hooks that would be run by `gclient runhooks`"""
1512 (options, args) = parser.parse_args(args)
1513 options.force = True
1514 client = GClient.LoadCurrentConfig(options)
1515 if not client:
1516 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1517 client.RunOnDeps(None, [])
1518 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1519 return 0
1520
1521
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001522def Command(name):
1523 return getattr(sys.modules[__name__], 'CMD' + name, None)
1524
1525
1526def CMDhelp(parser, args):
1527 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001528 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001529 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001530 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001531 parser.print_help()
1532 return 0
1533
1534
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001535def GenUsage(parser, command):
1536 """Modify an OptParse object with the function's documentation."""
1537 obj = Command(command)
1538 if command == 'help':
1539 command = '<command>'
1540 # OptParser.description prefer nicely non-formatted strings.
1541 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1542 usage = getattr(obj, 'usage', '')
1543 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1544 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001545
1546
maruel@chromium.org0895b752011-08-26 20:40:33 +00001547def Parser():
1548 """Returns the default parser."""
1549 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001550 # some arm boards have issues with parallel sync.
1551 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001552 jobs = 1
1553 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001554 jobs = max(8, gclient_utils.NumLocalCpus())
szager@chromium.orge2e03202012-07-31 18:05:16 +00001555 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001556 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001557 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001558 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001559 parser.add_option('-v', '--verbose', action='count', default=0,
1560 help='Produces additional output for diagnostics. Can be '
1561 'used up to three times for more logging info.')
1562 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001563 default=None,
1564 help='Specify an alternate %s file' % gclientfile_default)
1565 parser.add_option('--spec',
1566 default=None,
1567 help='create a gclient file containing the provided '
1568 'string. Due to Cygwin/Python brokenness, it '
1569 'probably can\'t contain any newlines.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001570 # Integrate standard options processing.
1571 old_parser = parser.parse_args
1572 def Parse(args):
1573 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001574 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1575 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001576 logging.basicConfig(level=level,
1577 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001578 if options.config_filename and options.spec:
1579 parser.error('Cannot specifiy both --gclientfile and --spec')
1580 if not options.config_filename:
1581 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001582 options.entries_filename = options.config_filename + '_entries'
1583 if options.jobs < 1:
1584 parser.error('--jobs must be 1 or higher')
1585
1586 # These hacks need to die.
1587 if not hasattr(options, 'revisions'):
1588 # GClient.RunOnDeps expects it even if not applicable.
1589 options.revisions = []
1590 if not hasattr(options, 'head'):
1591 options.head = None
1592 if not hasattr(options, 'nohooks'):
1593 options.nohooks = True
1594 if not hasattr(options, 'deps_os'):
1595 options.deps_os = None
1596 if not hasattr(options, 'manually_grab_svn_rev'):
1597 options.manually_grab_svn_rev = None
1598 if not hasattr(options, 'force'):
1599 options.force = None
1600 return (options, args)
1601 parser.parse_args = Parse
1602 # We don't want wordwrapping in epilog (usually examples)
1603 parser.format_epilog = lambda _: parser.epilog or ''
1604 return parser
1605
1606
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001607def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001608 """Doesn't parse the arguments here, just find the right subcommand to
1609 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001610 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001611 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001612 '\nYour python version %s is unsupported, please upgrade.\n' %
1613 sys.version.split(' ', 1)[0])
1614 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001615 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001616 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001617 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1618 # operations. Python as a strong tendency to buffer sys.stdout.
1619 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001620 # Make stdout annotated with the thread ids.
1621 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001622 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001623 # Unused variable 'usage'
1624 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001625 def to_str(fn):
1626 return (
1627 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1628 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1629 cmds = (
1630 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1631 )
1632 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001633 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001634 if argv:
1635 command = Command(argv[0])
1636 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001637 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001638 GenUsage(parser, argv[0])
1639 return command(parser, argv[1:])
1640 # Not a known command. Default to help.
1641 GenUsage(parser, 'help')
1642 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001643 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001644 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001645 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001646
1647
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001648if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001649 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001650 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001651
1652# vim: ts=2:sw=2:tw=80:et: