blob: 40a5e0d76b69b1848e0fc15ee83db90e40c9260d [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
peter@chromium.org1efccc82012-04-27 16:34:38 +000050
51Specifying a target OS
52 An optional key named "target_os" may be added to a gclient file to specify
53 one or more additional operating systems that should be considered when
54 processing the deps_os dict of a DEPS file.
55
56 Example:
57 target_os = [ "android" ]
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +000058
59 If the "target_os_only" key is also present and true, then *only* the
60 operating systems listed in "target_os" will be used.
61
62 Example:
63 target_os = [ "ios" ]
64 target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000065"""
66
maruel@chromium.org82798cb2012-02-23 18:16:12 +000067__version__ = "0.6.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000068
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000069import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000070import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071import optparse
72import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000073import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000074import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000075import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000079import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000081import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000082
maruel@chromium.org35625c72011-03-23 17:34:02 +000083import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000084import gclient_scm
85import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000086from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000087import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +000088from third_party import colorama
89# Import shortcut.
90from third_party.colorama import Fore
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000091
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000093def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000094 """Sets an attribute on a function."""
95 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000096 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000097 return fn
98 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000099
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000100
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000101## GClient implementation.
102
103
maruel@chromium.org116704f2010-06-11 17:34:38 +0000104class GClientKeywords(object):
105 class FromImpl(object):
106 """Used to implement the From() syntax."""
107
108 def __init__(self, module_name, sub_target_name=None):
109 """module_name is the dep module we want to include from. It can also be
110 the name of a subdirectory to include from.
111
112 sub_target_name is an optional parameter if the module name in the other
113 DEPS file is different. E.g., you might want to map src/net to net."""
114 self.module_name = module_name
115 self.sub_target_name = sub_target_name
116
117 def __str__(self):
118 return 'From(%s, %s)' % (repr(self.module_name),
119 repr(self.sub_target_name))
120
maruel@chromium.org116704f2010-06-11 17:34:38 +0000121 class FileImpl(object):
122 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000123 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000124
125 def __init__(self, file_location):
126 self.file_location = file_location
127
128 def __str__(self):
129 return 'File("%s")' % self.file_location
130
131 def GetPath(self):
132 return os.path.split(self.file_location)[0]
133
134 def GetFilename(self):
135 rev_tokens = self.file_location.split('@')
136 return os.path.split(rev_tokens[0])[1]
137
138 def GetRevision(self):
139 rev_tokens = self.file_location.split('@')
140 if len(rev_tokens) > 1:
141 return rev_tokens[1]
142 return None
143
144 class VarImpl(object):
145 def __init__(self, custom_vars, local_scope):
146 self._custom_vars = custom_vars
147 self._local_scope = local_scope
148
149 def Lookup(self, var_name):
150 """Implements the Var syntax."""
151 if var_name in self._custom_vars:
152 return self._custom_vars[var_name]
153 elif var_name in self._local_scope.get("vars", {}):
154 return self._local_scope["vars"][var_name]
155 raise gclient_utils.Error("Var is not defined: %s" % var_name)
156
157
maruel@chromium.org064186c2011-09-27 23:53:33 +0000158class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000159 """Immutable configuration settings."""
160 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000161 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000162 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000163 GClientKeywords.__init__(self)
164
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000165 # These are not mutable:
166 self._parent = parent
167 self._safesync_url = safesync_url
168 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000169 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000170 # 'managed' determines whether or not this dependency is synced/updated by
171 # gclient after gclient checks it out initially. The difference between
172 # 'managed' and 'should_process' is that the user specifies 'managed' via
173 # the --unmanaged command-line flag or a .gclient config, where
174 # 'should_process' is dynamically set by gclient if it goes over its
175 # recursion limit and controls gclient's behavior so it does not misbehave.
176 self._managed = managed
177 self._should_process = should_process
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000178 # This is a mutable value that overrides the normal recursion limit for this
179 # dependency. It is read from the actual DEPS file so cannot be set on
180 # class instantiation.
181 self.recursion_override = None
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000182 # This is a mutable value which has the list of 'target_os' OSes listed in
183 # the current deps file.
184 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000185
186 # These are only set in .gclient and not in DEPS files.
187 self._custom_vars = custom_vars or {}
188 self._custom_deps = custom_deps or {}
189
maruel@chromium.org064186c2011-09-27 23:53:33 +0000190 # Post process the url to remove trailing slashes.
191 if isinstance(self._url, basestring):
192 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
193 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000194 self._url = self._url.replace('/@', '@')
195 elif not isinstance(self._url,
196 (self.FromImpl, self.FileImpl, None.__class__)):
197 raise gclient_utils.Error(
198 ('dependency url must be either a string, None, '
199 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000200 if '/' in self._deps_file or '\\' in self._deps_file:
201 raise gclient_utils.Error('deps_file name must not be a path, just a '
202 'filename. %s' % self._deps_file)
203
204 @property
205 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000206 return self._deps_file
207
208 @property
209 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000210 return self._managed
211
212 @property
213 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 return self._parent
215
216 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000217 def root(self):
218 """Returns the root node, a GClient object."""
219 if not self.parent:
220 # This line is to signal pylint that it could be a GClient instance.
221 return self or GClient(None, None)
222 return self.parent.root
223
224 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000226 return self._safesync_url
227
228 @property
229 def should_process(self):
230 """True if this dependency should be processed, i.e. checked out."""
231 return self._should_process
232
233 @property
234 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000235 return self._custom_vars.copy()
236
237 @property
238 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 return self._custom_deps.copy()
240
maruel@chromium.org064186c2011-09-27 23:53:33 +0000241 @property
242 def url(self):
243 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000245 @property
246 def recursion_limit(self):
247 """Returns > 0 if this dependency is not too recursed to be processed."""
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000248 if self.recursion_override is not None:
249 return self.recursion_override
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000250 return max(self.parent.recursion_limit - 1, 0)
251
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000252 @property
253 def target_os(self):
254 if self.local_target_os is not None:
255 return tuple(set(self.local_target_os).union(self.parent.target_os))
256 else:
257 return self.parent.target_os
258
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000259 def get_custom_deps(self, name, url):
260 """Returns a custom deps if applicable."""
261 if self.parent:
262 url = self.parent.get_custom_deps(name, url)
263 # None is a valid return value to disable a dependency.
264 return self.custom_deps.get(name, url)
265
maruel@chromium.org064186c2011-09-27 23:53:33 +0000266
267class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000268 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000269
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000270 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000271 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000272 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000273 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000275 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000276
277 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000278 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000279
280 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000281 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000282 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000283 # A cache of the files affected by the current operation, necessary for
284 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000285 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000286 # If it is not set to True, the dependency wasn't processed for its child
287 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000288 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000289 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000290 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000291 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000292 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000293
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000294 if not self.name and self.parent:
295 raise gclient_utils.Error('Dependency without name')
296
maruel@chromium.org470b5432011-10-11 18:18:19 +0000297 @property
298 def requirements(self):
299 """Calculate the list of requirements."""
300 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000301 # self.parent is implicitly a requirement. This will be recursive by
302 # definition.
303 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000304 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000305
306 # For a tree with at least 2 levels*, the leaf node needs to depend
307 # on the level higher up in an orderly way.
308 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
309 # thus unsorted, while the .gclient format is a list thus sorted.
310 #
311 # * _recursion_limit is hard coded 2 and there is no hope to change this
312 # value.
313 #
314 # Interestingly enough, the following condition only works in the case we
315 # want: self is a 2nd level node. 3nd level node wouldn't need this since
316 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000317 if self.parent and self.parent.parent and not self.parent.parent.parent:
318 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000319
320 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000321 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000322
maruel@chromium.org470b5432011-10-11 18:18:19 +0000323 if self.name:
324 requirements |= set(
325 obj.name for obj in self.root.subtree(False)
326 if (obj is not self
327 and obj.name and
328 self.name.startswith(posixpath.join(obj.name, ''))))
329 requirements = tuple(sorted(requirements))
330 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
331 return requirements
332
333 def verify_validity(self):
334 """Verifies that this Dependency is fine to add as a child of another one.
335
336 Returns True if this entry should be added, False if it is a duplicate of
337 another entry.
338 """
339 logging.info('Dependency(%s).verify_validity()' % self.name)
340 if self.name in [s.name for s in self.parent.dependencies]:
341 raise gclient_utils.Error(
342 'The same name "%s" appears multiple times in the deps section' %
343 self.name)
344 if not self.should_process:
345 # Return early, no need to set requirements.
346 return True
347
348 # This require a full tree traversal with locks.
349 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
350 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000351 self_url = self.LateOverride(self.url)
352 sibling_url = sibling.LateOverride(sibling.url)
353 # Allow to have only one to be None or ''.
354 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000355 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000356 ('Dependency %s specified more than once:\n'
357 ' %s [%s]\n'
358 'vs\n'
359 ' %s [%s]') % (
360 self.name,
361 sibling.hierarchy(),
362 sibling_url,
363 self.hierarchy(),
364 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000365 # In theory we could keep it as a shadow of the other one. In
366 # practice, simply ignore it.
367 logging.warn('Won\'t process duplicate dependency %s' % sibling)
368 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000369 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000370
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000371 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000372 """Resolves the parsed url from url.
373
374 Manages From() keyword accordingly. Do not touch self.parsed_url nor
375 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000376 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000377 parsed_url = self.get_custom_deps(self.name, url)
378 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000379 logging.info(
380 'Dependency(%s).LateOverride(%s) -> %s' %
381 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000382 return parsed_url
383
384 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000385 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000386 ref = [
387 dep for dep in self.root.subtree(True) if url.module_name == dep.name
388 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000389 if not ref:
390 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
391 url.module_name, ref))
392 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000393 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000394 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000395 found_deps = [d for d in ref.dependencies if d.name == sub_target]
396 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000397 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000398 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
399 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000400 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000401
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000402 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000403 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000404 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000405 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000406 'Dependency(%s).LateOverride(%s) -> %s (From)' %
407 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000408 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000409
410 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000411 parsed_url = urlparse.urlparse(url)
412 if not parsed_url[0]:
413 # A relative url. Fetch the real base.
414 path = parsed_url[2]
415 if not path.startswith('/'):
416 raise gclient_utils.Error(
417 'relative DEPS entry \'%s\' must begin with a slash' % url)
418 # Create a scm just to query the full url.
419 parent_url = self.parent.parsed_url
420 if isinstance(parent_url, self.FileImpl):
421 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000422 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000423 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000424 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000425 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000426 logging.info(
427 'Dependency(%s).LateOverride(%s) -> %s' %
428 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000429 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000430
431 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000432 logging.info(
433 'Dependency(%s).LateOverride(%s) -> %s (File)' %
434 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000435 return url
436
437 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000438 logging.info(
439 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000440 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000441
442 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000443
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000444 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000445 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000446 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000447 assert not self.dependencies
448 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000449 local_scope = {}
450 var = self.VarImpl(self.custom_vars, local_scope)
451 global_scope = {
452 'File': self.FileImpl,
453 'From': self.FromImpl,
454 'Var': var.Lookup,
455 'deps_os': {},
456 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000457 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000458 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000459 logging.info(
460 'ParseDepsFile(%s): No %s file found at %s' % (
461 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000462 else:
463 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000464 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000465 # Eval the content.
466 try:
467 exec(deps_content, global_scope, local_scope)
468 except SyntaxError, e:
469 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000470 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000471 if 'recursion' in local_scope:
472 self.recursion_override = local_scope.get('recursion')
473 logging.warning(
474 'Setting %s recursion to %d.', self.name, self.recursion_limit)
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000475 # If present, save 'target_os' in the local_target_os property.
476 if 'target_os' in local_scope:
477 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000478 # load os specific dependencies if defined. these dependencies may
479 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000480 if 'deps_os' in local_scope:
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000481 for deps_os_key in self.target_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000482 os_deps = local_scope['deps_os'].get(deps_os_key, {})
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000483 if len(self.target_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000484 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000485 # platform, so we collect the broadest set of dependencies
486 # available. We may end up with the wrong revision of something for
487 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000488 deps.update([x for x in os_deps.items() if not x[0] in deps])
489 else:
490 deps.update(os_deps)
491
maruel@chromium.org271375b2010-06-23 19:17:38 +0000492 # If a line is in custom_deps, but not in the solution, we want to append
493 # this line to the solution.
494 for d in self.custom_deps:
495 if d not in deps:
496 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000497
498 # If use_relative_paths is set in the DEPS file, regenerate
499 # the dictionary using paths relative to the directory containing
500 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000501 use_relative_paths = local_scope.get('use_relative_paths', False)
502 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000503 rel_deps = {}
504 for d, url in deps.items():
505 # normpath is required to allow DEPS to use .. in their
506 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000507 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
508 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000509
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000510 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000511 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000512 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000513 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000514 deps_to_add.append(Dependency(
515 self, name, url, None, None, None, None,
516 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000517 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000518 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
519 logging.info('ParseDepsFile(%s) done' % self.name)
520
521 def add_dependencies_and_close(self, deps_to_add, hooks):
522 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000523 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000524 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000525 self.add_dependency(dep)
526 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000527
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000528 @staticmethod
529 def maybeGetParentRevision(
530 command, options, parsed_url, parent_name, revision_overrides):
531 """If we are performing an update and --transitive is set, set the
532 revision to the parent's revision. If we have an explicit revision
533 do nothing."""
534 if command == 'update' and options.transitive and not options.revision:
535 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
536 if not revision:
537 options.revision = revision_overrides.get(parent_name)
538 if options.verbose and options.revision:
539 print("Using parent's revision date: %s" % options.revision)
540 # If the parent has a revision override, then it must have been
541 # converted to date format.
542 assert (not options.revision or
543 gclient_utils.IsDateRevision(options.revision))
544
545 @staticmethod
546 def maybeConvertToDateRevision(
547 command, options, name, scm, revision_overrides):
548 """If we are performing an update and --transitive is set, convert the
549 revision to a date-revision (if necessary). Instead of having
550 -r 101 replace the revision with the time stamp of 101 (e.g.
551 "{2011-18-04}").
552 This way dependencies are upgraded to the revision they had at the
553 check-in of revision 101."""
554 if (command == 'update' and
555 options.transitive and
556 options.revision and
557 not gclient_utils.IsDateRevision(options.revision)):
558 revision_date = scm.GetRevisionDate(options.revision)
559 revision = gclient_utils.MakeDateRevision(revision_date)
560 if options.verbose:
561 print("Updating revision override from %s to %s." %
562 (options.revision, revision))
563 revision_overrides[name] = revision
564
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000565 # Arguments number differs from overridden method
566 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000567 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000568 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000569 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000570 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000571 if not self.should_process:
572 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000573 # When running runhooks, there's no need to consult the SCM.
574 # All known hooks are expected to run unconditionally regardless of working
575 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000576 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000577 parsed_url = self.LateOverride(self.url)
578 file_list = []
579 if run_scm and parsed_url:
580 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000581 # Special support for single-file checkout.
582 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000583 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
584 # pylint: disable=E1103
585 options.revision = parsed_url.GetRevision()
586 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000587 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000588 self.name)
589 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000590 args + [parsed_url.GetFilename()],
591 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000592 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000593 # Create a shallow copy to mutate revision.
594 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000595 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000596 self.maybeGetParentRevision(
597 command, options, parsed_url, self.parent.name, revision_overrides)
598 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
599 scm.RunCommand(command, options, args, file_list)
600 self.maybeConvertToDateRevision(
601 command, options, self.name, scm, revision_overrides)
602 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000603
604 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
605 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000606 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000607 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000608 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000609 continue
610 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000611 [self.root.root_dir.lower(), file_list[i].lower()])
612 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000613 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000614 while file_list[i].startswith(('\\', '/')):
615 file_list[i] = file_list[i][1:]
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +0000616 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000617 if not isinstance(parsed_url, self.FileImpl):
618 # Skip file only checkout.
619 scm = gclient_scm.GetScmName(parsed_url)
620 if not options.scm or scm in options.scm:
621 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
622 # Pass in the SCM type as an env variable
623 env = os.environ.copy()
624 if scm:
625 env['GCLIENT_SCM'] = scm
626 if parsed_url:
627 env['GCLIENT_URL'] = parsed_url
628 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000629 try:
630 gclient_utils.CheckCallAndFilter(
631 args, cwd=cwd, env=env, print_stdout=True)
632 except subprocess2.CalledProcessError:
633 if not options.ignore:
634 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000635 else:
636 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000637
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000638 # Always parse the DEPS file.
639 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000640
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000641 self._run_is_done(file_list, parsed_url)
642
643 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000644 # Parse the dependencies of this dependency.
645 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000646 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000647
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000648 @gclient_utils.lockedmethod
649 def _run_is_done(self, file_list, parsed_url):
650 # Both these are kept for hooks that are run as a separate tree traversal.
651 self._file_list = file_list
652 self._parsed_url = parsed_url
653 self._processed = True
654
szager@google.comb9a78d32012-03-13 18:46:21 +0000655 @staticmethod
656 def GetHookAction(hook_dict, matching_file_list):
657 """Turns a parsed 'hook' dict into an executable command."""
658 logging.debug(hook_dict)
659 logging.debug(matching_file_list)
660 command = hook_dict['action'][:]
661 if command[0] == 'python':
662 # If the hook specified "python" as the first item, the action is a
663 # Python script. Run it by starting a new copy of the same
664 # interpreter.
665 command[0] = sys.executable
666 if '$matching_files' in command:
667 splice_index = command.index('$matching_files')
668 command[splice_index:splice_index + 1] = matching_file_list
669 return command
670
671 def GetHooks(self, options):
672 """Evaluates all hooks, and return them in a flat list.
673
674 RunOnDeps() must have been called before to load the DEPS.
675 """
676 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000677 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000678 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000679 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000680 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000681 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000682 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000683 # TODO(maruel): If the user is using git or git-svn, then we don't know
684 # what files have changed so we always run all hooks. It'd be nice to fix
685 # that.
686 if (options.force or
687 isinstance(self.parsed_url, self.FileImpl) or
688 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000689 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000690 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000691 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000692 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000693 # Run hooks on the basis of whether the files from the gclient operation
694 # match each hook's pattern.
695 for hook_dict in self.deps_hooks:
696 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000697 matching_file_list = [
698 f for f in self.file_list_and_children if pattern.search(f)
699 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000700 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000701 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000702 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000703 result.extend(s.GetHooks(options))
704 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000705
szager@google.comb9a78d32012-03-13 18:46:21 +0000706 def RunHooksRecursively(self, options):
707 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000708 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000709 for hook in self.GetHooks(options):
710 try:
711 gclient_utils.CheckCallAndFilterAndHeader(
712 hook, cwd=self.root.root_dir, always=True)
713 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
714 # Use a discrete exit status code of 2 to indicate that a hook action
715 # failed. Users of this script may wish to treat hook action failures
716 # differently from VC failures.
717 print >> sys.stderr, 'Error: %s' % str(e)
718 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000719
maruel@chromium.org0d812442010-08-10 12:41:08 +0000720 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000721 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000722 dependencies = self.dependencies
723 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000724 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000725 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000726 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000727 for i in d.subtree(include_all):
728 yield i
729
730 def depth_first_tree(self):
731 """Depth-first recursion including the root node."""
732 yield self
733 for i in self.dependencies:
734 for j in i.depth_first_tree():
735 if j.should_process:
736 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000737
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000738 @gclient_utils.lockedmethod
739 def add_dependency(self, new_dep):
740 self._dependencies.append(new_dep)
741
742 @gclient_utils.lockedmethod
743 def _mark_as_parsed(self, new_hooks):
744 self._deps_hooks.extend(new_hooks)
745 self._deps_parsed = True
746
maruel@chromium.org68988972011-09-20 14:11:42 +0000747 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000748 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000749 def dependencies(self):
750 return tuple(self._dependencies)
751
752 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000753 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000754 def deps_hooks(self):
755 return tuple(self._deps_hooks)
756
757 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000758 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000759 def parsed_url(self):
760 return self._parsed_url
761
762 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000763 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000764 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000765 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000766 return self._deps_parsed
767
768 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000769 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000770 def processed(self):
771 return self._processed
772
773 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000774 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000775 def hooks_ran(self):
776 return self._hooks_ran
777
778 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000779 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000780 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000781 return tuple(self._file_list)
782
783 @property
784 def file_list_and_children(self):
785 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000786 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000787 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000788 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000789
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000790 def __str__(self):
791 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000792 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000793 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000794 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000795 # First try the native property if it exists.
796 if hasattr(self, '_' + i):
797 value = getattr(self, '_' + i, False)
798 else:
799 value = getattr(self, i, False)
800 if value:
801 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000802
803 for d in self.dependencies:
804 out.extend([' ' + x for x in str(d).splitlines()])
805 out.append('')
806 return '\n'.join(out)
807
808 def __repr__(self):
809 return '%s: %s' % (self.name, self.url)
810
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000811 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000812 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000813 out = '%s(%s)' % (self.name, self.url)
814 i = self.parent
815 while i and i.name:
816 out = '%s(%s) -> %s' % (i.name, i.url, out)
817 i = i.parent
818 return out
819
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000820
821class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000822 """Object that represent a gclient checkout. A tree of Dependency(), one per
823 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000824
825 DEPS_OS_CHOICES = {
826 "win32": "win",
827 "win": "win",
828 "cygwin": "win",
829 "darwin": "mac",
830 "mac": "mac",
831 "unix": "unix",
832 "linux": "unix",
833 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000834 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000835 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000836 }
837
838 DEFAULT_CLIENT_FILE_TEXT = ("""\
839solutions = [
840 { "name" : "%(solution_name)s",
841 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000842 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000843 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000844 "custom_deps" : {
845 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000846 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000847 },
848]
849""")
850
851 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
852 { "name" : "%(solution_name)s",
853 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000854 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000855 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000856 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000857%(solution_deps)s },
858 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000859 },
860""")
861
862 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
863# Snapshot generated with gclient revinfo --snapshot
864solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000865%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000866""")
867
868 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000869 # Do not change previous behavior. Only solution level and immediate DEPS
870 # are processed.
871 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000872 Dependency.__init__(self, None, None, None, None, True, None, None,
873 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000874 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000875 if options.deps_os:
876 enforced_os = options.deps_os.split(',')
877 else:
878 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
879 if 'all' in enforced_os:
880 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000881 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000882 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000883 self.config_content = None
884
885 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000886 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000887 config_dict = {}
888 self.config_content = content
889 try:
890 exec(content, config_dict)
891 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000892 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000893
peter@chromium.org1efccc82012-04-27 16:34:38 +0000894 # Append any target OS that is not already being enforced to the tuple.
895 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +0000896 if config_dict.get('target_os_only', False):
897 self._enforced_os = tuple(set(target_os))
898 else:
899 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
900
901 if not target_os and config_dict.get('target_os_only', False):
902 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
903 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +0000904
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000905 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000906 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000907 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000908 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000909 self, s['name'], s['url'],
910 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000911 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000912 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000913 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000914 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000915 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000916 except KeyError:
917 raise gclient_utils.Error('Invalid .gclient file. Solution is '
918 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000919 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
920 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000921
922 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000923 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000924 self._options.config_filename),
925 self.config_content)
926
927 @staticmethod
928 def LoadCurrentConfig(options):
929 """Searches for and loads a .gclient file relative to the current working
930 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +0000931 if options.spec:
932 client = GClient('.', options)
933 client.SetConfig(options.spec)
934 else:
935 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
936 if not path:
937 return None
938 client = GClient(path, options)
939 client.SetConfig(gclient_utils.FileRead(
940 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000941
942 if (options.revisions and
943 len(client.dependencies) > 1 and
944 any('@' not in r for r in options.revisions)):
945 print >> sys.stderr, (
946 'You must specify the full solution name like --revision %s@%s\n'
947 'when you have multiple solutions setup in your .gclient file.\n'
948 'Other solutions present are: %s.') % (
949 client.dependencies[0].name,
950 options.revisions[0],
951 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000952 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000953
nsylvain@google.comefc80932011-05-31 21:27:56 +0000954 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000955 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000956 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
957 'solution_name': solution_name,
958 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000959 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000960 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000961 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000962 })
963
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000964 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000965 """Creates a .gclient_entries file to record the list of unique checkouts.
966
967 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000968 """
969 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
970 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000971 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000972 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000973 # Skip over File() dependencies as we can't version them.
974 if not isinstance(entry.parsed_url, self.FileImpl):
975 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
976 pprint.pformat(entry.parsed_url))
977 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000978 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000979 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000980 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000981
982 def _ReadEntries(self):
983 """Read the .gclient_entries file for the given client.
984
985 Returns:
986 A sequence of solution names, which will be empty if there is the
987 entries file hasn't been created yet.
988 """
989 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000990 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000991 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000992 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000993 try:
994 exec(gclient_utils.FileRead(filename), scope)
995 except SyntaxError, e:
996 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000997 return scope['entries']
998
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000999 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001000 """Checks for revision overrides."""
1001 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +00001002 if self._options.head:
1003 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001004 # Do not check safesync_url if one or more --revision flag is specified.
1005 if not self._options.revisions:
1006 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001007 if not s.managed:
1008 self._options.revisions.append('%s@unmanaged' % s.name)
1009 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001010 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001011 if not self._options.revisions:
1012 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001013 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +00001014 index = 0
1015 for revision in self._options.revisions:
1016 if not '@' in revision:
1017 # Support for --revision 123
1018 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001019 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001020 if not sol in solutions_names:
1021 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
1022 print >> sys.stderr, ('Please fix your script, having invalid '
1023 '--revision flags will soon considered an error.')
1024 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001025 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +00001026 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001027 return revision_overrides
1028
dbeam@chromium.org051c88b2011-12-22 00:23:03 +00001029 def _ApplySafeSyncRev(self, dep):
1030 """Finds a valid revision from the content of the safesync_url and apply it
1031 by appending revisions to the revision list. Throws if revision appears to
1032 be invalid for the given |dep|."""
1033 assert len(dep.safesync_url) > 0
1034 handle = urllib.urlopen(dep.safesync_url)
1035 rev = handle.read().strip()
1036 handle.close()
1037 if not rev:
1038 raise gclient_utils.Error(
1039 'It appears your safesync_url (%s) is not working properly\n'
1040 '(as it returned an empty response). Check your config.' %
1041 dep.safesync_url)
1042 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
1043 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
1044 if self._options.verbose:
1045 print('Using safesync_url revision: %s.\n' % safe_rev)
1046 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1047
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001048 def RunOnDeps(self, command, args):
1049 """Runs a command on each dependency in a client and its dependencies.
1050
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001051 Args:
1052 command: The command to use (e.g., 'status' or 'diff')
1053 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001054 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001055 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001056 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001057 revision_overrides = {}
1058 # It's unnecessary to check for revision overrides for 'recurse'.
1059 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001060 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001061 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001062 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001063 # Disable progress for non-tty stdout.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001064 if (sys.stdout.isatty() and not self._options.verbose):
1065 if command in ('update', 'revert'):
1066 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001067 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001068 pm = Progress(' '.join(args), 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001069 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001070 for s in self.dependencies:
1071 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001072 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001073
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001074 # Once all the dependencies have been processed, it's now safe to run the
1075 # hooks.
1076 if not self._options.nohooks:
1077 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001078
1079 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001080 # Notify the user if there is an orphaned entry in their working copy.
1081 # Only delete the directory if there are no changes in it, and
1082 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001083 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001084 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001085 if not prev_url:
1086 # entry must have been overridden via .gclient custom_deps
1087 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001088 # Fix path separator on Windows.
1089 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001090 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001091 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001092 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001093 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001094 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001095 scm.status(self._options, [], file_list)
1096 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001097 if (not self._options.delete_unversioned_trees or
1098 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001099 # There are modified files in this entry. Keep warning until
1100 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001101 print(('\nWARNING: \'%s\' is no longer part of this client. '
1102 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001103 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001104 else:
1105 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001106 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001107 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001108 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001109 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001110 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001111 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001112
1113 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001114 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001115 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001116 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001117 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001118 for s in self.dependencies:
1119 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001120 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001121
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001122 def GetURLAndRev(dep):
1123 """Returns the revision-qualified SCM url for a Dependency."""
1124 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001125 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001126 if isinstance(dep.parsed_url, self.FileImpl):
1127 original_url = dep.parsed_url.file_location
1128 else:
1129 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001130 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001131 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001132 if not os.path.isdir(scm.checkout_path):
1133 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001134 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001136 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001137 new_gclient = ''
1138 # First level at .gclient
1139 for d in self.dependencies:
1140 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001141 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001142 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001143 for d in dep.dependencies:
1144 entries[d.name] = GetURLAndRev(d)
1145 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001146 GrabDeps(d)
1147 custom_deps = []
1148 for k in sorted(entries.keys()):
1149 if entries[k]:
1150 # Quotes aren't escaped...
1151 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1152 else:
1153 custom_deps.append(' \"%s\": None,\n' % k)
1154 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1155 'solution_name': d.name,
1156 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001157 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001158 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001159 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001160 'solution_deps': ''.join(custom_deps),
1161 }
1162 # Print the snapshot configuration file
1163 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001164 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001165 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001166 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001167 if self._options.actual:
1168 entries[d.name] = GetURLAndRev(d)
1169 else:
1170 entries[d.name] = d.parsed_url
1171 keys = sorted(entries.keys())
1172 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001173 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001174 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001175
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001176 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001177 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001178 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001179
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001180 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001181 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001182 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001183 return self._root_dir
1184
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001185 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001186 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001187 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001188 return self._enforced_os
1189
maruel@chromium.org68988972011-09-20 14:11:42 +00001190 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001191 def recursion_limit(self):
1192 """How recursive can each dependencies in DEPS file can load DEPS file."""
1193 return self._recursion_limit
1194
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001195 @property
1196 def target_os(self):
1197 return self._enforced_os
1198
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001199
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001200#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001201
1202
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001203def CMDcleanup(parser, args):
1204 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001205
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001206Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001207"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001208 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1209 help='override deps for the specified (comma-separated) '
1210 'platform(s); \'all\' will process all deps_os '
1211 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001212 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001213 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001214 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001215 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001216 if options.verbose:
1217 # Print out the .gclient file. This is longer than if we just printed the
1218 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001219 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001220 return client.RunOnDeps('cleanup', args)
1221
1222
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001223@attr('usage', '[command] [args ...]')
1224def CMDrecurse(parser, args):
1225 """Operates on all the entries.
1226
1227 Runs a shell command on all entries.
1228 """
1229 # Stop parsing at the first non-arg so that these go through to the command
1230 parser.disable_interspersed_args()
1231 parser.add_option('-s', '--scm', action='append', default=[],
1232 help='choose scm types to operate upon')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001233 parser.add_option('-i', '--ignore', action='store_true',
1234 help='continue processing in case of non zero return code')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001235 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001236 if not args:
1237 print >> sys.stderr, 'Need to supply a command!'
1238 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001239 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1240 if not root_and_entries:
1241 print >> sys.stderr, (
1242 'You need to run gclient sync at least once to use \'recurse\'.\n'
1243 'This is because .gclient_entries needs to exist and be up to date.')
1244 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001245
1246 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001247 scm_set = set()
1248 for scm in options.scm:
1249 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001250 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001251
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001252 options.nohooks = True
1253 client = GClient.LoadCurrentConfig(options)
1254 return client.RunOnDeps('recurse', args)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001255
1256
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001257@attr('usage', '[args ...]')
1258def CMDfetch(parser, args):
1259 """Fetches upstream commits for all modules.
1260
1261Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1262"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001263 (options, args) = parser.parse_args(args)
1264 args = ['-j%d' % options.jobs, '-s', 'git', 'git', 'fetch'] + args
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001265 return CMDrecurse(parser, args)
1266
1267
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001268@attr('usage', '[url] [safesync url]')
1269def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001270 """Create a .gclient file in the current directory.
1271
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001272This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001273top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001274modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001275provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001276URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001277"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001278
1279 # We do a little dance with the --gclientfile option. 'gclient config' is the
1280 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1281 # arguments. So, we temporarily stash any --gclientfile parameter into
1282 # options.output_config_file until after the (gclientfile xor spec) error
1283 # check.
1284 parser.remove_option('--gclientfile')
1285 parser.add_option('--gclientfile', dest='output_config_file',
1286 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001287 parser.add_option('--name',
1288 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001289 parser.add_option('--deps-file', default='DEPS',
1290 help='overrides the default name for the DEPS file for the'
1291 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001292 parser.add_option('--unmanaged', action='store_true', default=False,
1293 help='overrides the default behavior to make it possible '
1294 'to have the main solution untouched by gclient '
1295 '(gclient will check out unmanaged dependencies but '
1296 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001297 parser.add_option('--git-deps', action='store_true',
1298 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001299 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001300 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001301 if options.output_config_file:
1302 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001303 if ((options.spec and args) or len(args) > 2 or
1304 (not options.spec and not args)):
1305 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1306
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001307 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001308 if options.spec:
1309 client.SetConfig(options.spec)
1310 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001311 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001312 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001313 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001314 if name.endswith('.git'):
1315 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001316 else:
1317 # specify an alternate relpath for the given URL.
1318 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001319 deps_file = options.deps_file
1320 if options.git_deps:
1321 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001322 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001323 if len(args) > 1:
1324 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001325 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1326 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001327 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001328 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001329
1330
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001331@attr('epilog', """Example:
1332 gclient pack > patch.txt
1333 generate simple patch for configured client and dependences
1334""")
1335def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001336 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001337
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001338Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001339dependencies, and performs minimal postprocessing of the output. The
1340resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001341checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001342"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001343 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1344 help='override deps for the specified (comma-separated) '
1345 'platform(s); \'all\' will process all deps_os '
1346 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001347 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001348 (options, args) = parser.parse_args(args)
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001349 # Force jobs to 1 so the stdout is not annotated with the thread ids
1350 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001351 client = GClient.LoadCurrentConfig(options)
1352 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001353 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001354 if options.verbose:
1355 # Print out the .gclient file. This is longer than if we just printed the
1356 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001357 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001358 return client.RunOnDeps('pack', args)
1359
1360
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001361def CMDstatus(parser, args):
1362 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001363 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1364 help='override deps for the specified (comma-separated) '
1365 'platform(s); \'all\' will process all deps_os '
1366 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001367 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001368 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001369 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001370 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001371 if options.verbose:
1372 # Print out the .gclient file. This is longer than if we just printed the
1373 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001374 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001375 return client.RunOnDeps('status', args)
1376
1377
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001378@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001379 gclient sync
1380 update files from SCM according to current configuration,
1381 *for modules which have changed since last update or sync*
1382 gclient sync --force
1383 update files from SCM according to current configuration, for
1384 all modules (useful for recovering files deleted from local copy)
1385 gclient sync --revision src@31000
1386 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001387""")
1388def CMDsync(parser, args):
1389 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001390 parser.add_option('-f', '--force', action='store_true',
1391 help='force update even for unchanged modules')
1392 parser.add_option('-n', '--nohooks', action='store_true',
1393 help='don\'t run hooks after the update is complete')
1394 parser.add_option('-r', '--revision', action='append',
1395 dest='revisions', metavar='REV', default=[],
1396 help='Enforces revision/hash for the solutions with the '
1397 'format src@rev. The src@ part is optional and can be '
1398 'skipped. -r can be used multiple times when .gclient '
1399 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001400 'if the src@ part is skipped. Note that specifying '
1401 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001402 parser.add_option('-t', '--transitive', action='store_true',
1403 help='When a revision is specified (in the DEPS file or '
1404 'with the command-line flag), transitively update '
1405 'the dependencies to the date of the given revision. '
1406 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001407 parser.add_option('-H', '--head', action='store_true',
1408 help='skips any safesync_urls specified in '
1409 'configured solutions and sync to head instead')
1410 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001411 help='Deletes from the working copy any dependencies that '
1412 'have been removed since the last sync, as long as '
1413 'there are no local modifications. When used with '
1414 '--force, such dependencies are removed even if they '
1415 'have local modifications. When used with --reset, '
1416 'all untracked directories are removed from the '
1417 'working copy, exclusing those which are explicitly '
1418 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001419 parser.add_option('-R', '--reset', action='store_true',
1420 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001421 parser.add_option('-M', '--merge', action='store_true',
1422 help='merge upstream changes instead of trying to '
1423 'fast-forward or rebase')
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')
1428 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1429 help='Skip svn up whenever possible by requesting '
1430 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001431 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001432 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001433
1434 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001435 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001436
maruel@chromium.org307d1792010-05-31 20:03:13 +00001437 if options.revisions and options.head:
1438 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001439 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001440
1441 if options.verbose:
1442 # Print out the .gclient file. This is longer than if we just printed the
1443 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001444 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001445 return client.RunOnDeps('update', args)
1446
1447
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001448def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001449 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001450 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001451
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001452def CMDdiff(parser, args):
1453 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001454 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1455 help='override deps for the specified (comma-separated) '
1456 'platform(s); \'all\' will process all deps_os '
1457 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001458 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001459 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001460 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001461 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001462 if options.verbose:
1463 # Print out the .gclient file. This is longer than if we just printed the
1464 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001465 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001466 return client.RunOnDeps('diff', args)
1467
1468
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001469def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001470 """Revert all modifications in every dependencies.
1471
1472 That's the nuclear option to get back to a 'clean' state. It removes anything
1473 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001474 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1475 help='override deps for the specified (comma-separated) '
1476 'platform(s); \'all\' will process all deps_os '
1477 'references')
1478 parser.add_option('-n', '--nohooks', action='store_true',
1479 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001480 (options, args) = parser.parse_args(args)
1481 # --force is implied.
1482 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001483 options.reset = False
1484 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001485 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001486 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001487 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001488 return client.RunOnDeps('revert', args)
1489
1490
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001491def CMDrunhooks(parser, args):
1492 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001493 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1494 help='override deps for the specified (comma-separated) '
1495 'platform(s); \'all\' will process all deps_os '
1496 'references')
1497 parser.add_option('-f', '--force', action='store_true', default=True,
1498 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001499 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001500 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001501 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001502 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001503 if options.verbose:
1504 # Print out the .gclient file. This is longer than if we just printed the
1505 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001506 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001507 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001508 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001509 return client.RunOnDeps('runhooks', args)
1510
1511
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001512def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001513 """Output revision info mapping for the client and its dependencies.
1514
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001515 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001516 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001517 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1518 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001519 commit can change.
1520 """
1521 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1522 help='override deps for the specified (comma-separated) '
1523 'platform(s); \'all\' will process all deps_os '
1524 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001525 parser.add_option('-a', '--actual', action='store_true',
1526 help='gets the actual checked out revisions instead of the '
1527 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001528 parser.add_option('-s', '--snapshot', action='store_true',
1529 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001530 'version of all repositories to reproduce the tree, '
1531 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001532 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001533 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001534 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001535 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001536 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001537 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001538
1539
szager@google.comb9a78d32012-03-13 18:46:21 +00001540def CMDhookinfo(parser, args):
1541 """Output the hooks that would be run by `gclient runhooks`"""
1542 (options, args) = parser.parse_args(args)
1543 options.force = True
1544 client = GClient.LoadCurrentConfig(options)
1545 if not client:
1546 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1547 client.RunOnDeps(None, [])
1548 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1549 return 0
1550
1551
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001552def Command(name):
1553 return getattr(sys.modules[__name__], 'CMD' + name, None)
1554
1555
1556def CMDhelp(parser, args):
1557 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001558 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001559 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001560 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001561 parser.print_help()
1562 return 0
1563
1564
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001565def GenUsage(parser, command):
1566 """Modify an OptParse object with the function's documentation."""
1567 obj = Command(command)
1568 if command == 'help':
1569 command = '<command>'
1570 # OptParser.description prefer nicely non-formatted strings.
1571 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1572 usage = getattr(obj, 'usage', '')
1573 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1574 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001575
1576
maruel@chromium.org0895b752011-08-26 20:40:33 +00001577def Parser():
1578 """Returns the default parser."""
1579 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001580 # some arm boards have issues with parallel sync.
1581 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001582 jobs = 1
1583 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001584 jobs = max(8, gclient_utils.NumLocalCpus())
szager@chromium.orge2e03202012-07-31 18:05:16 +00001585 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001586 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001587 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001588 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001589 parser.add_option('-v', '--verbose', action='count', default=0,
1590 help='Produces additional output for diagnostics. Can be '
1591 'used up to three times for more logging info.')
1592 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001593 default=None,
1594 help='Specify an alternate %s file' % gclientfile_default)
1595 parser.add_option('--spec',
1596 default=None,
1597 help='create a gclient file containing the provided '
1598 'string. Due to Cygwin/Python brokenness, it '
1599 'probably can\'t contain any newlines.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001600 # Integrate standard options processing.
1601 old_parser = parser.parse_args
1602 def Parse(args):
1603 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001604 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1605 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001606 logging.basicConfig(level=level,
1607 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001608 if options.config_filename and options.spec:
1609 parser.error('Cannot specifiy both --gclientfile and --spec')
1610 if not options.config_filename:
1611 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001612 options.entries_filename = options.config_filename + '_entries'
1613 if options.jobs < 1:
1614 parser.error('--jobs must be 1 or higher')
1615
1616 # These hacks need to die.
1617 if not hasattr(options, 'revisions'):
1618 # GClient.RunOnDeps expects it even if not applicable.
1619 options.revisions = []
1620 if not hasattr(options, 'head'):
1621 options.head = None
1622 if not hasattr(options, 'nohooks'):
1623 options.nohooks = True
1624 if not hasattr(options, 'deps_os'):
1625 options.deps_os = None
1626 if not hasattr(options, 'manually_grab_svn_rev'):
1627 options.manually_grab_svn_rev = None
1628 if not hasattr(options, 'force'):
1629 options.force = None
1630 return (options, args)
1631 parser.parse_args = Parse
1632 # We don't want wordwrapping in epilog (usually examples)
1633 parser.format_epilog = lambda _: parser.epilog or ''
1634 return parser
1635
1636
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001637def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001638 """Doesn't parse the arguments here, just find the right subcommand to
1639 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001640 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001641 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001642 '\nYour python version %s is unsupported, please upgrade.\n' %
1643 sys.version.split(' ', 1)[0])
1644 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001645 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001646 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001647 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1648 # operations. Python as a strong tendency to buffer sys.stdout.
1649 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001650 # Make stdout annotated with the thread ids.
1651 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001652 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001653 # Unused variable 'usage'
1654 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001655 def to_str(fn):
1656 return (
1657 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1658 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1659 cmds = (
1660 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1661 )
1662 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001663 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001664 if argv:
1665 command = Command(argv[0])
1666 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001667 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001668 GenUsage(parser, argv[0])
1669 return command(parser, argv[1:])
1670 # Not a known command. Default to help.
1671 GenUsage(parser, 'help')
1672 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001673 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001674 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001675 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001676
1677
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001678if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001679 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001680 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001681
1682# vim: ts=2:sw=2:tw=80:et: