blob: 9065952bdfd959d56b56dbdf1be77ebfc3dd1c61 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
peter@chromium.org1efccc82012-04-27 16:34:38 +000050
51Specifying a target OS
52 An optional key named "target_os" may be added to a gclient file to specify
53 one or more additional operating systems that should be considered when
54 processing the deps_os dict of a DEPS file.
55
56 Example:
57 target_os = [ "android" ]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000058"""
59
maruel@chromium.org82798cb2012-02-23 18:16:12 +000060__version__ = "0.6.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000062import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000063import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000064import optparse
65import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000066import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000067import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000068import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000069import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000072import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000074import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000075
maruel@chromium.org35625c72011-03-23 17:34:02 +000076import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000077import gclient_scm
78import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000079from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000080import subprocess2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +000081from third_party import colorama
82# Import shortcut.
83from third_party.colorama import Fore
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000086def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000087 """Sets an attribute on a function."""
88 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000089 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000090 return fn
91 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000092
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000094## GClient implementation.
95
96
maruel@chromium.org116704f2010-06-11 17:34:38 +000097class GClientKeywords(object):
98 class FromImpl(object):
99 """Used to implement the From() syntax."""
100
101 def __init__(self, module_name, sub_target_name=None):
102 """module_name is the dep module we want to include from. It can also be
103 the name of a subdirectory to include from.
104
105 sub_target_name is an optional parameter if the module name in the other
106 DEPS file is different. E.g., you might want to map src/net to net."""
107 self.module_name = module_name
108 self.sub_target_name = sub_target_name
109
110 def __str__(self):
111 return 'From(%s, %s)' % (repr(self.module_name),
112 repr(self.sub_target_name))
113
maruel@chromium.org116704f2010-06-11 17:34:38 +0000114 class FileImpl(object):
115 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000116 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000117
118 def __init__(self, file_location):
119 self.file_location = file_location
120
121 def __str__(self):
122 return 'File("%s")' % self.file_location
123
124 def GetPath(self):
125 return os.path.split(self.file_location)[0]
126
127 def GetFilename(self):
128 rev_tokens = self.file_location.split('@')
129 return os.path.split(rev_tokens[0])[1]
130
131 def GetRevision(self):
132 rev_tokens = self.file_location.split('@')
133 if len(rev_tokens) > 1:
134 return rev_tokens[1]
135 return None
136
137 class VarImpl(object):
138 def __init__(self, custom_vars, local_scope):
139 self._custom_vars = custom_vars
140 self._local_scope = local_scope
141
142 def Lookup(self, var_name):
143 """Implements the Var syntax."""
144 if var_name in self._custom_vars:
145 return self._custom_vars[var_name]
146 elif var_name in self._local_scope.get("vars", {}):
147 return self._local_scope["vars"][var_name]
148 raise gclient_utils.Error("Var is not defined: %s" % var_name)
149
150
maruel@chromium.org064186c2011-09-27 23:53:33 +0000151class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000152 """Immutable configuration settings."""
153 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000154 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000155 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000156 GClientKeywords.__init__(self)
157
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000158 # These are not mutable:
159 self._parent = parent
160 self._safesync_url = safesync_url
161 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000162 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000163 # 'managed' determines whether or not this dependency is synced/updated by
164 # gclient after gclient checks it out initially. The difference between
165 # 'managed' and 'should_process' is that the user specifies 'managed' via
166 # the --unmanaged command-line flag or a .gclient config, where
167 # 'should_process' is dynamically set by gclient if it goes over its
168 # recursion limit and controls gclient's behavior so it does not misbehave.
169 self._managed = managed
170 self._should_process = should_process
171
172 # These are only set in .gclient and not in DEPS files.
173 self._custom_vars = custom_vars or {}
174 self._custom_deps = custom_deps or {}
175
maruel@chromium.org064186c2011-09-27 23:53:33 +0000176 # Post process the url to remove trailing slashes.
177 if isinstance(self._url, basestring):
178 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
179 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000180 self._url = self._url.replace('/@', '@')
181 elif not isinstance(self._url,
182 (self.FromImpl, self.FileImpl, None.__class__)):
183 raise gclient_utils.Error(
184 ('dependency url must be either a string, None, '
185 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000186 if '/' in self._deps_file or '\\' in self._deps_file:
187 raise gclient_utils.Error('deps_file name must not be a path, just a '
188 'filename. %s' % self._deps_file)
189
190 @property
191 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000192 return self._deps_file
193
194 @property
195 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000196 return self._managed
197
198 @property
199 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000200 return self._parent
201
202 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000203 def root(self):
204 """Returns the root node, a GClient object."""
205 if not self.parent:
206 # This line is to signal pylint that it could be a GClient instance.
207 return self or GClient(None, None)
208 return self.parent.root
209
210 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000212 return self._safesync_url
213
214 @property
215 def should_process(self):
216 """True if this dependency should be processed, i.e. checked out."""
217 return self._should_process
218
219 @property
220 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000221 return self._custom_vars.copy()
222
223 @property
224 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 return self._custom_deps.copy()
226
maruel@chromium.org064186c2011-09-27 23:53:33 +0000227 @property
228 def url(self):
229 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000230
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000231 @property
232 def recursion_limit(self):
233 """Returns > 0 if this dependency is not too recursed to be processed."""
234 return max(self.parent.recursion_limit - 1, 0)
235
236 def get_custom_deps(self, name, url):
237 """Returns a custom deps if applicable."""
238 if self.parent:
239 url = self.parent.get_custom_deps(name, url)
240 # None is a valid return value to disable a dependency.
241 return self.custom_deps.get(name, url)
242
maruel@chromium.org064186c2011-09-27 23:53:33 +0000243
244class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000245 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000246
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000247 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000248 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000249 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000250 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000251 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000252 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000253
254 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000255 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000256
257 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000258 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000259 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000260 # A cache of the files affected by the current operation, necessary for
261 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000262 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000263 # If it is not set to True, the dependency wasn't processed for its child
264 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000265 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000266 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000267 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000268 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000269 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000270
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000271 if not self.name and self.parent:
272 raise gclient_utils.Error('Dependency without name')
273
maruel@chromium.org470b5432011-10-11 18:18:19 +0000274 @property
275 def requirements(self):
276 """Calculate the list of requirements."""
277 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000278 # self.parent is implicitly a requirement. This will be recursive by
279 # definition.
280 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000281 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000282
283 # For a tree with at least 2 levels*, the leaf node needs to depend
284 # on the level higher up in an orderly way.
285 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
286 # thus unsorted, while the .gclient format is a list thus sorted.
287 #
288 # * _recursion_limit is hard coded 2 and there is no hope to change this
289 # value.
290 #
291 # Interestingly enough, the following condition only works in the case we
292 # want: self is a 2nd level node. 3nd level node wouldn't need this since
293 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000294 if self.parent and self.parent.parent and not self.parent.parent.parent:
295 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000296
297 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000298 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000299
maruel@chromium.org470b5432011-10-11 18:18:19 +0000300 if self.name:
301 requirements |= set(
302 obj.name for obj in self.root.subtree(False)
303 if (obj is not self
304 and obj.name and
305 self.name.startswith(posixpath.join(obj.name, ''))))
306 requirements = tuple(sorted(requirements))
307 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
308 return requirements
309
310 def verify_validity(self):
311 """Verifies that this Dependency is fine to add as a child of another one.
312
313 Returns True if this entry should be added, False if it is a duplicate of
314 another entry.
315 """
316 logging.info('Dependency(%s).verify_validity()' % self.name)
317 if self.name in [s.name for s in self.parent.dependencies]:
318 raise gclient_utils.Error(
319 'The same name "%s" appears multiple times in the deps section' %
320 self.name)
321 if not self.should_process:
322 # Return early, no need to set requirements.
323 return True
324
325 # This require a full tree traversal with locks.
326 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
327 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000328 self_url = self.LateOverride(self.url)
329 sibling_url = sibling.LateOverride(sibling.url)
330 # Allow to have only one to be None or ''.
331 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000332 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000333 ('Dependency %s specified more than once:\n'
334 ' %s [%s]\n'
335 'vs\n'
336 ' %s [%s]') % (
337 self.name,
338 sibling.hierarchy(),
339 sibling_url,
340 self.hierarchy(),
341 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000342 # In theory we could keep it as a shadow of the other one. In
343 # practice, simply ignore it.
344 logging.warn('Won\'t process duplicate dependency %s' % sibling)
345 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000346 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000347
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000348 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000349 """Resolves the parsed url from url.
350
351 Manages From() keyword accordingly. Do not touch self.parsed_url nor
352 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000353 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000354 parsed_url = self.get_custom_deps(self.name, url)
355 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000356 logging.info(
357 'Dependency(%s).LateOverride(%s) -> %s' %
358 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000359 return parsed_url
360
361 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000362 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000363 ref = [
364 dep for dep in self.root.subtree(True) if url.module_name == dep.name
365 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000366 if not ref:
367 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
368 url.module_name, ref))
369 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000370 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000371 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000372 found_deps = [d for d in ref.dependencies if d.name == sub_target]
373 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000374 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000375 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
376 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000377 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000378
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000379 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000380 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000381 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000382 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000383 'Dependency(%s).LateOverride(%s) -> %s (From)' %
384 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000385 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000386
387 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000388 parsed_url = urlparse.urlparse(url)
389 if not parsed_url[0]:
390 # A relative url. Fetch the real base.
391 path = parsed_url[2]
392 if not path.startswith('/'):
393 raise gclient_utils.Error(
394 'relative DEPS entry \'%s\' must begin with a slash' % url)
395 # Create a scm just to query the full url.
396 parent_url = self.parent.parsed_url
397 if isinstance(parent_url, self.FileImpl):
398 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000399 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000400 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000401 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000402 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000403 logging.info(
404 'Dependency(%s).LateOverride(%s) -> %s' %
405 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000406 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000407
408 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000409 logging.info(
410 'Dependency(%s).LateOverride(%s) -> %s (File)' %
411 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000412 return url
413
414 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000415 logging.info(
416 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000417 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000418
419 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000420
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000421 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000422 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000423 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000424 assert not self.dependencies
425 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000426 local_scope = {}
427 var = self.VarImpl(self.custom_vars, local_scope)
428 global_scope = {
429 'File': self.FileImpl,
430 'From': self.FromImpl,
431 'Var': var.Lookup,
432 'deps_os': {},
433 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000434 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000435 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000436 logging.info(
437 'ParseDepsFile(%s): No %s file found at %s' % (
438 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000439 else:
440 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000441 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000442 # Eval the content.
443 try:
444 exec(deps_content, global_scope, local_scope)
445 except SyntaxError, e:
446 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000447 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000448 # load os specific dependencies if defined. these dependencies may
449 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000450 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000451 enforced_os = self.root.enforced_os
452 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000453 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000454 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000455 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000456 # platform, so we collect the broadest set of dependencies
457 # available. We may end up with the wrong revision of something for
458 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000459 deps.update([x for x in os_deps.items() if not x[0] in deps])
460 else:
461 deps.update(os_deps)
462
maruel@chromium.org271375b2010-06-23 19:17:38 +0000463 # If a line is in custom_deps, but not in the solution, we want to append
464 # this line to the solution.
465 for d in self.custom_deps:
466 if d not in deps:
467 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000468
469 # If use_relative_paths is set in the DEPS file, regenerate
470 # the dictionary using paths relative to the directory containing
471 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000472 use_relative_paths = local_scope.get('use_relative_paths', False)
473 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000474 rel_deps = {}
475 for d, url in deps.items():
476 # normpath is required to allow DEPS to use .. in their
477 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000478 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
479 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000480
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000481 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000482 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000483 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000484 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000485 deps_to_add.append(Dependency(
486 self, name, url, None, None, None, None,
487 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000488 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000489 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
490 logging.info('ParseDepsFile(%s) done' % self.name)
491
492 def add_dependencies_and_close(self, deps_to_add, hooks):
493 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000494 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000495 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000496 self.add_dependency(dep)
497 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000498
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000499 @staticmethod
500 def maybeGetParentRevision(
501 command, options, parsed_url, parent_name, revision_overrides):
502 """If we are performing an update and --transitive is set, set the
503 revision to the parent's revision. If we have an explicit revision
504 do nothing."""
505 if command == 'update' and options.transitive and not options.revision:
506 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
507 if not revision:
508 options.revision = revision_overrides.get(parent_name)
509 if options.verbose and options.revision:
510 print("Using parent's revision date: %s" % options.revision)
511 # If the parent has a revision override, then it must have been
512 # converted to date format.
513 assert (not options.revision or
514 gclient_utils.IsDateRevision(options.revision))
515
516 @staticmethod
517 def maybeConvertToDateRevision(
518 command, options, name, scm, revision_overrides):
519 """If we are performing an update and --transitive is set, convert the
520 revision to a date-revision (if necessary). Instead of having
521 -r 101 replace the revision with the time stamp of 101 (e.g.
522 "{2011-18-04}").
523 This way dependencies are upgraded to the revision they had at the
524 check-in of revision 101."""
525 if (command == 'update' and
526 options.transitive and
527 options.revision and
528 not gclient_utils.IsDateRevision(options.revision)):
529 revision_date = scm.GetRevisionDate(options.revision)
530 revision = gclient_utils.MakeDateRevision(revision_date)
531 if options.verbose:
532 print("Updating revision override from %s to %s." %
533 (options.revision, revision))
534 revision_overrides[name] = revision
535
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000536 # Arguments number differs from overridden method
537 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000538 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000539 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000540 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000541 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000542 if not self.should_process:
543 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000544 # When running runhooks, there's no need to consult the SCM.
545 # All known hooks are expected to run unconditionally regardless of working
546 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000547 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000548 parsed_url = self.LateOverride(self.url)
549 file_list = []
550 if run_scm and parsed_url:
551 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000552 # Special support for single-file checkout.
553 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000554 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
555 # pylint: disable=E1103
556 options.revision = parsed_url.GetRevision()
557 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000558 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000559 self.name)
560 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000561 args + [parsed_url.GetFilename()],
562 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000563 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000564 # Create a shallow copy to mutate revision.
565 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000566 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000567 self.maybeGetParentRevision(
568 command, options, parsed_url, self.parent.name, revision_overrides)
569 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
570 scm.RunCommand(command, options, args, file_list)
571 self.maybeConvertToDateRevision(
572 command, options, self.name, scm, revision_overrides)
573 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000574
575 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
576 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000577 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000578 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000579 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000580 continue
581 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000582 [self.root.root_dir.lower(), file_list[i].lower()])
583 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000584 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000585 while file_list[i].startswith(('\\', '/')):
586 file_list[i] = file_list[i][1:]
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +0000587 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000588 if not isinstance(parsed_url, self.FileImpl):
589 # Skip file only checkout.
590 scm = gclient_scm.GetScmName(parsed_url)
591 if not options.scm or scm in options.scm:
592 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
593 # Pass in the SCM type as an env variable
594 env = os.environ.copy()
595 if scm:
596 env['GCLIENT_SCM'] = scm
597 if parsed_url:
598 env['GCLIENT_URL'] = parsed_url
599 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000600 try:
601 gclient_utils.CheckCallAndFilter(
602 args, cwd=cwd, env=env, print_stdout=True)
603 except subprocess2.CalledProcessError:
604 if not options.ignore:
605 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000606 else:
607 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000608
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000609 # Always parse the DEPS file.
610 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000611
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000612 self._run_is_done(file_list, parsed_url)
613
614 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000615 # Parse the dependencies of this dependency.
616 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000617 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000618
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000619 @gclient_utils.lockedmethod
620 def _run_is_done(self, file_list, parsed_url):
621 # Both these are kept for hooks that are run as a separate tree traversal.
622 self._file_list = file_list
623 self._parsed_url = parsed_url
624 self._processed = True
625
szager@google.comb9a78d32012-03-13 18:46:21 +0000626 @staticmethod
627 def GetHookAction(hook_dict, matching_file_list):
628 """Turns a parsed 'hook' dict into an executable command."""
629 logging.debug(hook_dict)
630 logging.debug(matching_file_list)
631 command = hook_dict['action'][:]
632 if command[0] == 'python':
633 # If the hook specified "python" as the first item, the action is a
634 # Python script. Run it by starting a new copy of the same
635 # interpreter.
636 command[0] = sys.executable
637 if '$matching_files' in command:
638 splice_index = command.index('$matching_files')
639 command[splice_index:splice_index + 1] = matching_file_list
640 return command
641
642 def GetHooks(self, options):
643 """Evaluates all hooks, and return them in a flat list.
644
645 RunOnDeps() must have been called before to load the DEPS.
646 """
647 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000648 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000649 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000650 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000651 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000652 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000653 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000654 # TODO(maruel): If the user is using git or git-svn, then we don't know
655 # what files have changed so we always run all hooks. It'd be nice to fix
656 # that.
657 if (options.force or
658 isinstance(self.parsed_url, self.FileImpl) or
659 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000660 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000661 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000662 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000663 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000664 # Run hooks on the basis of whether the files from the gclient operation
665 # match each hook's pattern.
666 for hook_dict in self.deps_hooks:
667 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000668 matching_file_list = [
669 f for f in self.file_list_and_children if pattern.search(f)
670 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000671 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000672 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000673 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000674 result.extend(s.GetHooks(options))
675 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000676
szager@google.comb9a78d32012-03-13 18:46:21 +0000677 def RunHooksRecursively(self, options):
678 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000679 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000680 for hook in self.GetHooks(options):
681 try:
682 gclient_utils.CheckCallAndFilterAndHeader(
683 hook, cwd=self.root.root_dir, always=True)
684 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
685 # Use a discrete exit status code of 2 to indicate that a hook action
686 # failed. Users of this script may wish to treat hook action failures
687 # differently from VC failures.
688 print >> sys.stderr, 'Error: %s' % str(e)
689 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000690
maruel@chromium.org0d812442010-08-10 12:41:08 +0000691 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000692 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000693 dependencies = self.dependencies
694 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000695 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000696 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000697 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000698 for i in d.subtree(include_all):
699 yield i
700
701 def depth_first_tree(self):
702 """Depth-first recursion including the root node."""
703 yield self
704 for i in self.dependencies:
705 for j in i.depth_first_tree():
706 if j.should_process:
707 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000708
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000709 @gclient_utils.lockedmethod
710 def add_dependency(self, new_dep):
711 self._dependencies.append(new_dep)
712
713 @gclient_utils.lockedmethod
714 def _mark_as_parsed(self, new_hooks):
715 self._deps_hooks.extend(new_hooks)
716 self._deps_parsed = True
717
maruel@chromium.org68988972011-09-20 14:11:42 +0000718 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000719 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000720 def dependencies(self):
721 return tuple(self._dependencies)
722
723 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000724 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000725 def deps_hooks(self):
726 return tuple(self._deps_hooks)
727
728 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000729 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000730 def parsed_url(self):
731 return self._parsed_url
732
733 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000734 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000735 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000736 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000737 return self._deps_parsed
738
739 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000740 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000741 def processed(self):
742 return self._processed
743
744 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000745 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000746 def hooks_ran(self):
747 return self._hooks_ran
748
749 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000750 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000751 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000752 return tuple(self._file_list)
753
754 @property
755 def file_list_and_children(self):
756 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000757 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000758 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000759 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000760
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000761 def __str__(self):
762 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000763 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000764 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000765 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000766 # First try the native property if it exists.
767 if hasattr(self, '_' + i):
768 value = getattr(self, '_' + i, False)
769 else:
770 value = getattr(self, i, False)
771 if value:
772 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000773
774 for d in self.dependencies:
775 out.extend([' ' + x for x in str(d).splitlines()])
776 out.append('')
777 return '\n'.join(out)
778
779 def __repr__(self):
780 return '%s: %s' % (self.name, self.url)
781
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000782 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000783 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000784 out = '%s(%s)' % (self.name, self.url)
785 i = self.parent
786 while i and i.name:
787 out = '%s(%s) -> %s' % (i.name, i.url, out)
788 i = i.parent
789 return out
790
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000791
792class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000793 """Object that represent a gclient checkout. A tree of Dependency(), one per
794 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000795
796 DEPS_OS_CHOICES = {
797 "win32": "win",
798 "win": "win",
799 "cygwin": "win",
800 "darwin": "mac",
801 "mac": "mac",
802 "unix": "unix",
803 "linux": "unix",
804 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000805 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000806 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000807 }
808
809 DEFAULT_CLIENT_FILE_TEXT = ("""\
810solutions = [
811 { "name" : "%(solution_name)s",
812 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000813 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000814 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000815 "custom_deps" : {
816 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000817 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000818 },
819]
820""")
821
822 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
823 { "name" : "%(solution_name)s",
824 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000825 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000826 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000827 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000828%(solution_deps)s },
829 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000830 },
831""")
832
833 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
834# Snapshot generated with gclient revinfo --snapshot
835solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000836%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000837""")
838
839 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000840 # Do not change previous behavior. Only solution level and immediate DEPS
841 # are processed.
842 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000843 Dependency.__init__(self, None, None, None, None, True, None, None,
844 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000845 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000846 if options.deps_os:
847 enforced_os = options.deps_os.split(',')
848 else:
849 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
850 if 'all' in enforced_os:
851 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000852 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000853 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000854 self.config_content = None
855
856 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000857 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000858 config_dict = {}
859 self.config_content = content
860 try:
861 exec(content, config_dict)
862 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000863 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000864
peter@chromium.org1efccc82012-04-27 16:34:38 +0000865 # Append any target OS that is not already being enforced to the tuple.
866 target_os = config_dict.get('target_os', [])
867 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
868
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000869 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000870 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000871 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000872 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000873 self, s['name'], s['url'],
874 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000875 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000876 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000877 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000878 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000879 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000880 except KeyError:
881 raise gclient_utils.Error('Invalid .gclient file. Solution is '
882 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000883 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
884 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000885
886 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000887 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000888 self._options.config_filename),
889 self.config_content)
890
891 @staticmethod
892 def LoadCurrentConfig(options):
893 """Searches for and loads a .gclient file relative to the current working
894 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +0000895 if options.spec:
896 client = GClient('.', options)
897 client.SetConfig(options.spec)
898 else:
899 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
900 if not path:
901 return None
902 client = GClient(path, options)
903 client.SetConfig(gclient_utils.FileRead(
904 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000905
906 if (options.revisions and
907 len(client.dependencies) > 1 and
908 any('@' not in r for r in options.revisions)):
909 print >> sys.stderr, (
910 'You must specify the full solution name like --revision %s@%s\n'
911 'when you have multiple solutions setup in your .gclient file.\n'
912 'Other solutions present are: %s.') % (
913 client.dependencies[0].name,
914 options.revisions[0],
915 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000916 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000917
nsylvain@google.comefc80932011-05-31 21:27:56 +0000918 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000919 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000920 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
921 'solution_name': solution_name,
922 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000923 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000924 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000925 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000926 })
927
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000928 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000929 """Creates a .gclient_entries file to record the list of unique checkouts.
930
931 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000932 """
933 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
934 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000935 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000936 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000937 # Skip over File() dependencies as we can't version them.
938 if not isinstance(entry.parsed_url, self.FileImpl):
939 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
940 pprint.pformat(entry.parsed_url))
941 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000942 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000943 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000944 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000945
946 def _ReadEntries(self):
947 """Read the .gclient_entries file for the given client.
948
949 Returns:
950 A sequence of solution names, which will be empty if there is the
951 entries file hasn't been created yet.
952 """
953 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000954 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000955 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000956 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000957 try:
958 exec(gclient_utils.FileRead(filename), scope)
959 except SyntaxError, e:
960 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000961 return scope['entries']
962
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000963 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000964 """Checks for revision overrides."""
965 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000966 if self._options.head:
967 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000968 # Do not check safesync_url if one or more --revision flag is specified.
969 if not self._options.revisions:
970 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000971 if not s.managed:
972 self._options.revisions.append('%s@unmanaged' % s.name)
973 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000974 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000975 if not self._options.revisions:
976 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000977 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000978 index = 0
979 for revision in self._options.revisions:
980 if not '@' in revision:
981 # Support for --revision 123
982 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000983 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000984 if not sol in solutions_names:
985 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
986 print >> sys.stderr, ('Please fix your script, having invalid '
987 '--revision flags will soon considered an error.')
988 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000989 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000990 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000991 return revision_overrides
992
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000993 def _ApplySafeSyncRev(self, dep):
994 """Finds a valid revision from the content of the safesync_url and apply it
995 by appending revisions to the revision list. Throws if revision appears to
996 be invalid for the given |dep|."""
997 assert len(dep.safesync_url) > 0
998 handle = urllib.urlopen(dep.safesync_url)
999 rev = handle.read().strip()
1000 handle.close()
1001 if not rev:
1002 raise gclient_utils.Error(
1003 'It appears your safesync_url (%s) is not working properly\n'
1004 '(as it returned an empty response). Check your config.' %
1005 dep.safesync_url)
1006 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
1007 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
1008 if self._options.verbose:
1009 print('Using safesync_url revision: %s.\n' % safe_rev)
1010 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1011
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001012 def RunOnDeps(self, command, args):
1013 """Runs a command on each dependency in a client and its dependencies.
1014
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001015 Args:
1016 command: The command to use (e.g., 'status' or 'diff')
1017 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001018 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001019 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001020 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001021 revision_overrides = {}
1022 # It's unnecessary to check for revision overrides for 'recurse'.
1023 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001024 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001025 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001026 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001027 # Disable progress for non-tty stdout.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001028 if (sys.stdout.isatty() and not self._options.verbose):
1029 if command in ('update', 'revert'):
1030 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001031 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001032 pm = Progress(' '.join(args), 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001033 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001034 for s in self.dependencies:
1035 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001036 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001037
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001038 # Once all the dependencies have been processed, it's now safe to run the
1039 # hooks.
1040 if not self._options.nohooks:
1041 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042
1043 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001044 # Notify the user if there is an orphaned entry in their working copy.
1045 # Only delete the directory if there are no changes in it, and
1046 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001047 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001048 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001049 if not prev_url:
1050 # entry must have been overridden via .gclient custom_deps
1051 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001052 # Fix path separator on Windows.
1053 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001054 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001055 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001056 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001057 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001058 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001059 scm.status(self._options, [], file_list)
1060 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001061 if (not self._options.delete_unversioned_trees or
1062 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001063 # There are modified files in this entry. Keep warning until
1064 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001065 print(('\nWARNING: \'%s\' is no longer part of this client. '
1066 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001067 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001068 else:
1069 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001070 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001071 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001072 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001073 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001074 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001075 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001076
1077 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001078 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001079 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001080 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001081 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001082 for s in self.dependencies:
1083 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001084 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001085
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001086 def GetURLAndRev(dep):
1087 """Returns the revision-qualified SCM url for a Dependency."""
1088 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001089 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001090 if isinstance(dep.parsed_url, self.FileImpl):
1091 original_url = dep.parsed_url.file_location
1092 else:
1093 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001094 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001095 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001096 if not os.path.isdir(scm.checkout_path):
1097 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001098 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001099
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001100 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001101 new_gclient = ''
1102 # First level at .gclient
1103 for d in self.dependencies:
1104 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001105 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001106 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001107 for d in dep.dependencies:
1108 entries[d.name] = GetURLAndRev(d)
1109 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001110 GrabDeps(d)
1111 custom_deps = []
1112 for k in sorted(entries.keys()):
1113 if entries[k]:
1114 # Quotes aren't escaped...
1115 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1116 else:
1117 custom_deps.append(' \"%s\": None,\n' % k)
1118 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1119 'solution_name': d.name,
1120 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001121 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001122 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001123 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001124 'solution_deps': ''.join(custom_deps),
1125 }
1126 # Print the snapshot configuration file
1127 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001128 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001129 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001130 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001131 if self._options.actual:
1132 entries[d.name] = GetURLAndRev(d)
1133 else:
1134 entries[d.name] = d.parsed_url
1135 keys = sorted(entries.keys())
1136 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001137 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001138 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001139
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001140 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001141 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001142 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001143
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001144 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001145 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001146 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001147 return self._root_dir
1148
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001149 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001150 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001151 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001152 return self._enforced_os
1153
maruel@chromium.org68988972011-09-20 14:11:42 +00001154 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001155 def recursion_limit(self):
1156 """How recursive can each dependencies in DEPS file can load DEPS file."""
1157 return self._recursion_limit
1158
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001160#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001161
1162
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001163def CMDcleanup(parser, args):
1164 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001165
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001166Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001167"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001168 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1169 help='override deps for the specified (comma-separated) '
1170 'platform(s); \'all\' will process all deps_os '
1171 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001172 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001173 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001174 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001175 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001176 if options.verbose:
1177 # Print out the .gclient file. This is longer than if we just printed the
1178 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001179 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001180 return client.RunOnDeps('cleanup', args)
1181
1182
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001183@attr('usage', '[command] [args ...]')
1184def CMDrecurse(parser, args):
1185 """Operates on all the entries.
1186
1187 Runs a shell command on all entries.
1188 """
1189 # Stop parsing at the first non-arg so that these go through to the command
1190 parser.disable_interspersed_args()
1191 parser.add_option('-s', '--scm', action='append', default=[],
1192 help='choose scm types to operate upon')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001193 parser.add_option('-i', '--ignore', action='store_true',
1194 help='continue processing in case of non zero return code')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001195 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001196 if not args:
1197 print >> sys.stderr, 'Need to supply a command!'
1198 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001199 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1200 if not root_and_entries:
1201 print >> sys.stderr, (
1202 'You need to run gclient sync at least once to use \'recurse\'.\n'
1203 'This is because .gclient_entries needs to exist and be up to date.')
1204 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001205
1206 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001207 scm_set = set()
1208 for scm in options.scm:
1209 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001210 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001211
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001212 options.nohooks = True
1213 client = GClient.LoadCurrentConfig(options)
1214 return client.RunOnDeps('recurse', args)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001215
1216
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001217@attr('usage', '[args ...]')
1218def CMDfetch(parser, args):
1219 """Fetches upstream commits for all modules.
1220
1221Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1222"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001223 (options, args) = parser.parse_args(args)
1224 args = ['-j%d' % options.jobs, '-s', 'git', 'git', 'fetch'] + args
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001225 return CMDrecurse(parser, args)
1226
1227
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001228@attr('usage', '[url] [safesync url]')
1229def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001230 """Create a .gclient file in the current directory.
1231
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001232This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001233top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001234modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001235provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001236URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001237"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001238
1239 # We do a little dance with the --gclientfile option. 'gclient config' is the
1240 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1241 # arguments. So, we temporarily stash any --gclientfile parameter into
1242 # options.output_config_file until after the (gclientfile xor spec) error
1243 # check.
1244 parser.remove_option('--gclientfile')
1245 parser.add_option('--gclientfile', dest='output_config_file',
1246 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001247 parser.add_option('--name',
1248 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001249 parser.add_option('--deps-file', default='DEPS',
1250 help='overrides the default name for the DEPS file for the'
1251 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001252 parser.add_option('--unmanaged', action='store_true', default=False,
1253 help='overrides the default behavior to make it possible '
1254 'to have the main solution untouched by gclient '
1255 '(gclient will check out unmanaged dependencies but '
1256 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001257 parser.add_option('--git-deps', action='store_true',
1258 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001259 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001260 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001261 if options.output_config_file:
1262 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001263 if ((options.spec and args) or len(args) > 2 or
1264 (not options.spec and not args)):
1265 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1266
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001267 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001268 if options.spec:
1269 client.SetConfig(options.spec)
1270 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001271 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001272 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001273 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001274 if name.endswith('.git'):
1275 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001276 else:
1277 # specify an alternate relpath for the given URL.
1278 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001279 deps_file = options.deps_file
1280 if options.git_deps:
1281 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001282 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001283 if len(args) > 1:
1284 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001285 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1286 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001287 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001288 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001289
1290
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001291@attr('epilog', """Example:
1292 gclient pack > patch.txt
1293 generate simple patch for configured client and dependences
1294""")
1295def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001296 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001297
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001298Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001299dependencies, and performs minimal postprocessing of the output. The
1300resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001301checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001302"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001303 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1304 help='override deps for the specified (comma-separated) '
1305 'platform(s); \'all\' will process all deps_os '
1306 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001307 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001308 (options, args) = parser.parse_args(args)
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001309 # Force jobs to 1 so the stdout is not annotated with the thread ids
1310 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001311 client = GClient.LoadCurrentConfig(options)
1312 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001313 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001314 if options.verbose:
1315 # Print out the .gclient file. This is longer than if we just printed the
1316 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001317 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001318 return client.RunOnDeps('pack', args)
1319
1320
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001321def CMDstatus(parser, args):
1322 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001323 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1324 help='override deps for the specified (comma-separated) '
1325 'platform(s); \'all\' will process all deps_os '
1326 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001327 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001328 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001329 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001330 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001331 if options.verbose:
1332 # Print out the .gclient file. This is longer than if we just printed the
1333 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001334 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001335 return client.RunOnDeps('status', args)
1336
1337
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001338@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001339 gclient sync
1340 update files from SCM according to current configuration,
1341 *for modules which have changed since last update or sync*
1342 gclient sync --force
1343 update files from SCM according to current configuration, for
1344 all modules (useful for recovering files deleted from local copy)
1345 gclient sync --revision src@31000
1346 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001347""")
1348def CMDsync(parser, args):
1349 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001350 parser.add_option('-f', '--force', action='store_true',
1351 help='force update even for unchanged modules')
1352 parser.add_option('-n', '--nohooks', action='store_true',
1353 help='don\'t run hooks after the update is complete')
1354 parser.add_option('-r', '--revision', action='append',
1355 dest='revisions', metavar='REV', default=[],
1356 help='Enforces revision/hash for the solutions with the '
1357 'format src@rev. The src@ part is optional and can be '
1358 'skipped. -r can be used multiple times when .gclient '
1359 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001360 'if the src@ part is skipped. Note that specifying '
1361 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001362 parser.add_option('-t', '--transitive', action='store_true',
1363 help='When a revision is specified (in the DEPS file or '
1364 'with the command-line flag), transitively update '
1365 'the dependencies to the date of the given revision. '
1366 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001367 parser.add_option('-H', '--head', action='store_true',
1368 help='skips any safesync_urls specified in '
1369 'configured solutions and sync to head instead')
1370 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001371 help='Deletes from the working copy any dependencies that '
1372 'have been removed since the last sync, as long as '
1373 'there are no local modifications. When used with '
1374 '--force, such dependencies are removed even if they '
1375 'have local modifications. When used with --reset, '
1376 'all untracked directories are removed from the '
1377 'working copy, exclusing those which are explicitly '
1378 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001379 parser.add_option('-R', '--reset', action='store_true',
1380 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001381 parser.add_option('-M', '--merge', action='store_true',
1382 help='merge upstream changes instead of trying to '
1383 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001384 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1385 help='override deps for the specified (comma-separated) '
1386 'platform(s); \'all\' will process all deps_os '
1387 'references')
1388 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1389 help='Skip svn up whenever possible by requesting '
1390 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001391 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001392 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001393
1394 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001395 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001396
maruel@chromium.org307d1792010-05-31 20:03:13 +00001397 if options.revisions and options.head:
1398 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001399 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001400
1401 if options.verbose:
1402 # Print out the .gclient file. This is longer than if we just printed the
1403 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001404 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001405 return client.RunOnDeps('update', args)
1406
1407
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001408def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001409 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001410 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001411
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001412def CMDdiff(parser, args):
1413 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001414 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1415 help='override deps for the specified (comma-separated) '
1416 'platform(s); \'all\' will process all deps_os '
1417 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001418 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001419 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001420 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001421 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001422 if options.verbose:
1423 # Print out the .gclient file. This is longer than if we just printed the
1424 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001425 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001426 return client.RunOnDeps('diff', args)
1427
1428
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001429def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001430 """Revert all modifications in every dependencies.
1431
1432 That's the nuclear option to get back to a 'clean' state. It removes anything
1433 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001434 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1435 help='override deps for the specified (comma-separated) '
1436 'platform(s); \'all\' will process all deps_os '
1437 'references')
1438 parser.add_option('-n', '--nohooks', action='store_true',
1439 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001440 (options, args) = parser.parse_args(args)
1441 # --force is implied.
1442 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001443 options.reset = False
1444 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001445 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001446 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001447 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001448 return client.RunOnDeps('revert', args)
1449
1450
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001451def CMDrunhooks(parser, args):
1452 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001453 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1454 help='override deps for the specified (comma-separated) '
1455 'platform(s); \'all\' will process all deps_os '
1456 'references')
1457 parser.add_option('-f', '--force', action='store_true', default=True,
1458 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001459 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001460 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001461 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001462 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001463 if options.verbose:
1464 # Print out the .gclient file. This is longer than if we just printed the
1465 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001466 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001467 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001468 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001469 return client.RunOnDeps('runhooks', args)
1470
1471
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001472def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001473 """Output revision info mapping for the client and its dependencies.
1474
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001475 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001476 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001477 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1478 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001479 commit can change.
1480 """
1481 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1482 help='override deps for the specified (comma-separated) '
1483 'platform(s); \'all\' will process all deps_os '
1484 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001485 parser.add_option('-a', '--actual', action='store_true',
1486 help='gets the actual checked out revisions instead of the '
1487 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001488 parser.add_option('-s', '--snapshot', action='store_true',
1489 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001490 'version of all repositories to reproduce the tree, '
1491 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001492 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001493 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001494 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001495 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001496 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001497 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001498
1499
szager@google.comb9a78d32012-03-13 18:46:21 +00001500def CMDhookinfo(parser, args):
1501 """Output the hooks that would be run by `gclient runhooks`"""
1502 (options, args) = parser.parse_args(args)
1503 options.force = True
1504 client = GClient.LoadCurrentConfig(options)
1505 if not client:
1506 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1507 client.RunOnDeps(None, [])
1508 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1509 return 0
1510
1511
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001512def Command(name):
1513 return getattr(sys.modules[__name__], 'CMD' + name, None)
1514
1515
1516def CMDhelp(parser, args):
1517 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001518 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001519 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001520 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001521 parser.print_help()
1522 return 0
1523
1524
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001525def GenUsage(parser, command):
1526 """Modify an OptParse object with the function's documentation."""
1527 obj = Command(command)
1528 if command == 'help':
1529 command = '<command>'
1530 # OptParser.description prefer nicely non-formatted strings.
1531 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1532 usage = getattr(obj, 'usage', '')
1533 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1534 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001535
1536
maruel@chromium.org0895b752011-08-26 20:40:33 +00001537def Parser():
1538 """Returns the default parser."""
1539 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001540 # some arm boards have issues with parallel sync.
1541 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001542 jobs = 1
1543 else:
ilevy@chromium.org13691502012-10-16 04:26:37 +00001544 jobs = max(8, gclient_utils.NumLocalCpus())
szager@chromium.orge2e03202012-07-31 18:05:16 +00001545 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001546 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001547 help='Specify how many SCM commands can run in parallel; '
ilevy@chromium.org13691502012-10-16 04:26:37 +00001548 'defaults to number of cpu cores (%default)')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001549 parser.add_option('-v', '--verbose', action='count', default=0,
1550 help='Produces additional output for diagnostics. Can be '
1551 'used up to three times for more logging info.')
1552 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001553 default=None,
1554 help='Specify an alternate %s file' % gclientfile_default)
1555 parser.add_option('--spec',
1556 default=None,
1557 help='create a gclient file containing the provided '
1558 'string. Due to Cygwin/Python brokenness, it '
1559 'probably can\'t contain any newlines.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001560 # Integrate standard options processing.
1561 old_parser = parser.parse_args
1562 def Parse(args):
1563 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001564 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1565 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001566 logging.basicConfig(level=level,
1567 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001568 if options.config_filename and options.spec:
1569 parser.error('Cannot specifiy both --gclientfile and --spec')
1570 if not options.config_filename:
1571 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001572 options.entries_filename = options.config_filename + '_entries'
1573 if options.jobs < 1:
1574 parser.error('--jobs must be 1 or higher')
1575
1576 # These hacks need to die.
1577 if not hasattr(options, 'revisions'):
1578 # GClient.RunOnDeps expects it even if not applicable.
1579 options.revisions = []
1580 if not hasattr(options, 'head'):
1581 options.head = None
1582 if not hasattr(options, 'nohooks'):
1583 options.nohooks = True
1584 if not hasattr(options, 'deps_os'):
1585 options.deps_os = None
1586 if not hasattr(options, 'manually_grab_svn_rev'):
1587 options.manually_grab_svn_rev = None
1588 if not hasattr(options, 'force'):
1589 options.force = None
1590 return (options, args)
1591 parser.parse_args = Parse
1592 # We don't want wordwrapping in epilog (usually examples)
1593 parser.format_epilog = lambda _: parser.epilog or ''
1594 return parser
1595
1596
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001597def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001598 """Doesn't parse the arguments here, just find the right subcommand to
1599 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001600 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001601 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001602 '\nYour python version %s is unsupported, please upgrade.\n' %
1603 sys.version.split(' ', 1)[0])
1604 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001605 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001606 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001607 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1608 # operations. Python as a strong tendency to buffer sys.stdout.
1609 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001610 # Make stdout annotated with the thread ids.
1611 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001612 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001613 # Unused variable 'usage'
1614 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001615 def to_str(fn):
1616 return (
1617 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1618 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1619 cmds = (
1620 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1621 )
1622 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001623 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001624 if argv:
1625 command = Command(argv[0])
1626 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001627 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001628 GenUsage(parser, argv[0])
1629 return command(parser, argv[1:])
1630 # Not a known command. Default to help.
1631 GenUsage(parser, 'help')
1632 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001633 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001634 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001635 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001636
1637
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001638if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001639 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001640 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001641
1642# vim: ts=2:sw=2:tw=80:et: