blob: b9d9991af3c6919c118bb114a5ab370d6e14762a [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:
328 if self.url != sibling.url:
329 raise gclient_utils.Error(
330 'Dependency %s specified more than once:\n %s\nvs\n %s' %
331 (self.name, sibling.hierarchy(), self.hierarchy()))
332 # In theory we could keep it as a shadow of the other one. In
333 # practice, simply ignore it.
334 logging.warn('Won\'t process duplicate dependency %s' % sibling)
335 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000336 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000337
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000338 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000339 """Resolves the parsed url from url.
340
341 Manages From() keyword accordingly. Do not touch self.parsed_url nor
342 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000343 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000344 parsed_url = self.get_custom_deps(self.name, url)
345 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000346 logging.info(
347 'Dependency(%s).LateOverride(%s) -> %s' %
348 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000349 return parsed_url
350
351 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000352 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000353 ref = [
354 dep for dep in self.root.subtree(True) if url.module_name == dep.name
355 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000356 if not ref:
357 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
358 url.module_name, ref))
359 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000360 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000361 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000362 found_deps = [d for d in ref.dependencies if d.name == sub_target]
363 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000364 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000365 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
366 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000367 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000368
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000369 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000370 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000371 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000372 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000373 'Dependency(%s).LateOverride(%s) -> %s (From)' %
374 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000375 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000376
377 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000378 parsed_url = urlparse.urlparse(url)
379 if not parsed_url[0]:
380 # A relative url. Fetch the real base.
381 path = parsed_url[2]
382 if not path.startswith('/'):
383 raise gclient_utils.Error(
384 'relative DEPS entry \'%s\' must begin with a slash' % url)
385 # Create a scm just to query the full url.
386 parent_url = self.parent.parsed_url
387 if isinstance(parent_url, self.FileImpl):
388 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000389 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000390 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000391 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000392 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000393 logging.info(
394 'Dependency(%s).LateOverride(%s) -> %s' %
395 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000396 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000397
398 if isinstance(url, self.FileImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000399 logging.info(
400 'Dependency(%s).LateOverride(%s) -> %s (File)' %
401 (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000402 return url
403
404 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000405 logging.info(
406 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000407 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000408
409 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000410
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000411 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000412 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000413 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000414 assert not self.dependencies
415 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000416 local_scope = {}
417 var = self.VarImpl(self.custom_vars, local_scope)
418 global_scope = {
419 'File': self.FileImpl,
420 'From': self.FromImpl,
421 'Var': var.Lookup,
422 'deps_os': {},
423 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000424 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000425 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000426 logging.info(
427 'ParseDepsFile(%s): No %s file found at %s' % (
428 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000429 else:
430 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000431 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000432 # Eval the content.
433 try:
434 exec(deps_content, global_scope, local_scope)
435 except SyntaxError, e:
436 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000437 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000438 # load os specific dependencies if defined. these dependencies may
439 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000440 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000441 enforced_os = self.root.enforced_os
442 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000443 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000444 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000445 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000446 # platform, so we collect the broadest set of dependencies
447 # available. We may end up with the wrong revision of something for
448 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000449 deps.update([x for x in os_deps.items() if not x[0] in deps])
450 else:
451 deps.update(os_deps)
452
maruel@chromium.org271375b2010-06-23 19:17:38 +0000453 # If a line is in custom_deps, but not in the solution, we want to append
454 # this line to the solution.
455 for d in self.custom_deps:
456 if d not in deps:
457 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000458
459 # If use_relative_paths is set in the DEPS file, regenerate
460 # the dictionary using paths relative to the directory containing
461 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000462 use_relative_paths = local_scope.get('use_relative_paths', False)
463 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000464 rel_deps = {}
465 for d, url in deps.items():
466 # normpath is required to allow DEPS to use .. in their
467 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000468 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
469 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000470
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000471 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000472 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000473 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000474 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000475 deps_to_add.append(Dependency(
476 self, name, url, None, None, None, None,
477 self.deps_file, should_process))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000478 deps_to_add.sort(key=lambda x: x.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000479 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
480 logging.info('ParseDepsFile(%s) done' % self.name)
481
482 def add_dependencies_and_close(self, deps_to_add, hooks):
483 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000484 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000485 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000486 self.add_dependency(dep)
487 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000488
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000489 @staticmethod
490 def maybeGetParentRevision(
491 command, options, parsed_url, parent_name, revision_overrides):
492 """If we are performing an update and --transitive is set, set the
493 revision to the parent's revision. If we have an explicit revision
494 do nothing."""
495 if command == 'update' and options.transitive and not options.revision:
496 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
497 if not revision:
498 options.revision = revision_overrides.get(parent_name)
499 if options.verbose and options.revision:
500 print("Using parent's revision date: %s" % options.revision)
501 # If the parent has a revision override, then it must have been
502 # converted to date format.
503 assert (not options.revision or
504 gclient_utils.IsDateRevision(options.revision))
505
506 @staticmethod
507 def maybeConvertToDateRevision(
508 command, options, name, scm, revision_overrides):
509 """If we are performing an update and --transitive is set, convert the
510 revision to a date-revision (if necessary). Instead of having
511 -r 101 replace the revision with the time stamp of 101 (e.g.
512 "{2011-18-04}").
513 This way dependencies are upgraded to the revision they had at the
514 check-in of revision 101."""
515 if (command == 'update' and
516 options.transitive and
517 options.revision and
518 not gclient_utils.IsDateRevision(options.revision)):
519 revision_date = scm.GetRevisionDate(options.revision)
520 revision = gclient_utils.MakeDateRevision(revision_date)
521 if options.verbose:
522 print("Updating revision override from %s to %s." %
523 (options.revision, revision))
524 revision_overrides[name] = revision
525
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000526 # Arguments number differs from overridden method
527 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000528 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000529 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000530 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000531 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000532 if not self.should_process:
533 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000534 # When running runhooks, there's no need to consult the SCM.
535 # All known hooks are expected to run unconditionally regardless of working
536 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000537 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000538 parsed_url = self.LateOverride(self.url)
539 file_list = []
540 if run_scm and parsed_url:
541 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000542 # Special support for single-file checkout.
543 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000544 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
545 # pylint: disable=E1103
546 options.revision = parsed_url.GetRevision()
547 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000548 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000549 self.name)
550 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000551 args + [parsed_url.GetFilename()],
552 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000553 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000554 # Create a shallow copy to mutate revision.
555 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000556 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000557 self.maybeGetParentRevision(
558 command, options, parsed_url, self.parent.name, revision_overrides)
559 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
560 scm.RunCommand(command, options, args, file_list)
561 self.maybeConvertToDateRevision(
562 command, options, self.name, scm, revision_overrides)
563 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000564
565 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
566 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000567 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000568 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000569 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000570 continue
571 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000572 [self.root.root_dir.lower(), file_list[i].lower()])
573 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000574 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000575 while file_list[i].startswith(('\\', '/')):
576 file_list[i] = file_list[i][1:]
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000577 elif command is 'recurse':
578 if not isinstance(parsed_url, self.FileImpl):
579 # Skip file only checkout.
580 scm = gclient_scm.GetScmName(parsed_url)
581 if not options.scm or scm in options.scm:
582 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
583 # Pass in the SCM type as an env variable
584 env = os.environ.copy()
585 if scm:
586 env['GCLIENT_SCM'] = scm
587 if parsed_url:
588 env['GCLIENT_URL'] = parsed_url
589 if os.path.isdir(cwd):
maruel@chromium.org288054d2012-03-05 00:43:07 +0000590 try:
591 gclient_utils.CheckCallAndFilter(
592 args, cwd=cwd, env=env, print_stdout=True)
593 except subprocess2.CalledProcessError:
594 if not options.ignore:
595 raise
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000596 else:
597 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000598
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000599 # Always parse the DEPS file.
600 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000601
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000602 self._run_is_done(file_list, parsed_url)
603
604 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000605 # Parse the dependencies of this dependency.
606 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000607 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000608
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000609 @gclient_utils.lockedmethod
610 def _run_is_done(self, file_list, parsed_url):
611 # Both these are kept for hooks that are run as a separate tree traversal.
612 self._file_list = file_list
613 self._parsed_url = parsed_url
614 self._processed = True
615
szager@google.comb9a78d32012-03-13 18:46:21 +0000616 @staticmethod
617 def GetHookAction(hook_dict, matching_file_list):
618 """Turns a parsed 'hook' dict into an executable command."""
619 logging.debug(hook_dict)
620 logging.debug(matching_file_list)
621 command = hook_dict['action'][:]
622 if command[0] == 'python':
623 # If the hook specified "python" as the first item, the action is a
624 # Python script. Run it by starting a new copy of the same
625 # interpreter.
626 command[0] = sys.executable
627 if '$matching_files' in command:
628 splice_index = command.index('$matching_files')
629 command[splice_index:splice_index + 1] = matching_file_list
630 return command
631
632 def GetHooks(self, options):
633 """Evaluates all hooks, and return them in a flat list.
634
635 RunOnDeps() must have been called before to load the DEPS.
636 """
637 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000638 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000639 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000640 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000641 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000642 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000643 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000644 # TODO(maruel): If the user is using git or git-svn, then we don't know
645 # what files have changed so we always run all hooks. It'd be nice to fix
646 # that.
647 if (options.force or
648 isinstance(self.parsed_url, self.FileImpl) or
649 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000650 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000651 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000652 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000653 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000654 # Run hooks on the basis of whether the files from the gclient operation
655 # match each hook's pattern.
656 for hook_dict in self.deps_hooks:
657 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000658 matching_file_list = [
659 f for f in self.file_list_and_children if pattern.search(f)
660 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000661 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000662 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000663 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000664 result.extend(s.GetHooks(options))
665 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000666
szager@google.comb9a78d32012-03-13 18:46:21 +0000667 def RunHooksRecursively(self, options):
668 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000669 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000670 for hook in self.GetHooks(options):
671 try:
672 gclient_utils.CheckCallAndFilterAndHeader(
673 hook, cwd=self.root.root_dir, always=True)
674 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
675 # Use a discrete exit status code of 2 to indicate that a hook action
676 # failed. Users of this script may wish to treat hook action failures
677 # differently from VC failures.
678 print >> sys.stderr, 'Error: %s' % str(e)
679 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000680
maruel@chromium.org0d812442010-08-10 12:41:08 +0000681 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000682 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000683 dependencies = self.dependencies
684 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000685 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000686 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000687 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000688 for i in d.subtree(include_all):
689 yield i
690
691 def depth_first_tree(self):
692 """Depth-first recursion including the root node."""
693 yield self
694 for i in self.dependencies:
695 for j in i.depth_first_tree():
696 if j.should_process:
697 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000698
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000699 @gclient_utils.lockedmethod
700 def add_dependency(self, new_dep):
701 self._dependencies.append(new_dep)
702
703 @gclient_utils.lockedmethod
704 def _mark_as_parsed(self, new_hooks):
705 self._deps_hooks.extend(new_hooks)
706 self._deps_parsed = True
707
maruel@chromium.org68988972011-09-20 14:11:42 +0000708 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000709 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000710 def dependencies(self):
711 return tuple(self._dependencies)
712
713 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000714 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000715 def deps_hooks(self):
716 return tuple(self._deps_hooks)
717
718 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000719 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000720 def parsed_url(self):
721 return self._parsed_url
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_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000726 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000727 return self._deps_parsed
728
729 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000730 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000731 def processed(self):
732 return self._processed
733
734 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000735 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000736 def hooks_ran(self):
737 return self._hooks_ran
738
739 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000740 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000741 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000742 return tuple(self._file_list)
743
744 @property
745 def file_list_and_children(self):
746 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000747 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000748 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000749 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000750
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000751 def __str__(self):
752 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000753 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000754 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000755 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000756 # First try the native property if it exists.
757 if hasattr(self, '_' + i):
758 value = getattr(self, '_' + i, False)
759 else:
760 value = getattr(self, i, False)
761 if value:
762 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000763
764 for d in self.dependencies:
765 out.extend([' ' + x for x in str(d).splitlines()])
766 out.append('')
767 return '\n'.join(out)
768
769 def __repr__(self):
770 return '%s: %s' % (self.name, self.url)
771
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000772 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000773 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000774 out = '%s(%s)' % (self.name, self.url)
775 i = self.parent
776 while i and i.name:
777 out = '%s(%s) -> %s' % (i.name, i.url, out)
778 i = i.parent
779 return out
780
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000781
782class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000783 """Object that represent a gclient checkout. A tree of Dependency(), one per
784 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000785
786 DEPS_OS_CHOICES = {
787 "win32": "win",
788 "win": "win",
789 "cygwin": "win",
790 "darwin": "mac",
791 "mac": "mac",
792 "unix": "unix",
793 "linux": "unix",
794 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000795 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +0000796 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000797 }
798
799 DEFAULT_CLIENT_FILE_TEXT = ("""\
800solutions = [
801 { "name" : "%(solution_name)s",
802 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000803 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000804 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000805 "custom_deps" : {
806 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000807 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000808 },
809]
810""")
811
812 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
813 { "name" : "%(solution_name)s",
814 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000815 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000816 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000817 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000818%(solution_deps)s },
819 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000820 },
821""")
822
823 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
824# Snapshot generated with gclient revinfo --snapshot
825solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000826%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000827""")
828
829 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000830 # Do not change previous behavior. Only solution level and immediate DEPS
831 # are processed.
832 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000833 Dependency.__init__(self, None, None, None, None, True, None, None,
834 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000835 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000836 if options.deps_os:
837 enforced_os = options.deps_os.split(',')
838 else:
839 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
840 if 'all' in enforced_os:
841 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000842 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000843 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000844 self.config_content = None
845
846 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000847 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000848 config_dict = {}
849 self.config_content = content
850 try:
851 exec(content, config_dict)
852 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000853 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000854
peter@chromium.org1efccc82012-04-27 16:34:38 +0000855 # Append any target OS that is not already being enforced to the tuple.
856 target_os = config_dict.get('target_os', [])
857 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
858
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000859 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000860 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000861 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000862 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000863 self, s['name'], s['url'],
864 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000865 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000866 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000867 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000868 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000869 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000870 except KeyError:
871 raise gclient_utils.Error('Invalid .gclient file. Solution is '
872 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000873 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
874 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000875
876 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000877 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000878 self._options.config_filename),
879 self.config_content)
880
881 @staticmethod
882 def LoadCurrentConfig(options):
883 """Searches for and loads a .gclient file relative to the current working
884 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +0000885 if options.spec:
886 client = GClient('.', options)
887 client.SetConfig(options.spec)
888 else:
889 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
890 if not path:
891 return None
892 client = GClient(path, options)
893 client.SetConfig(gclient_utils.FileRead(
894 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +0000895
896 if (options.revisions and
897 len(client.dependencies) > 1 and
898 any('@' not in r for r in options.revisions)):
899 print >> sys.stderr, (
900 'You must specify the full solution name like --revision %s@%s\n'
901 'when you have multiple solutions setup in your .gclient file.\n'
902 'Other solutions present are: %s.') % (
903 client.dependencies[0].name,
904 options.revisions[0],
905 ', '.join(s.name for s in client.dependencies[1:]))
maruel@chromium.org15804092010-09-02 17:07:37 +0000906 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000907
nsylvain@google.comefc80932011-05-31 21:27:56 +0000908 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000909 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000910 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
911 'solution_name': solution_name,
912 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000913 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000914 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000915 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000916 })
917
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000918 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000919 """Creates a .gclient_entries file to record the list of unique checkouts.
920
921 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000922 """
923 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
924 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000925 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000926 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000927 # Skip over File() dependencies as we can't version them.
928 if not isinstance(entry.parsed_url, self.FileImpl):
929 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
930 pprint.pformat(entry.parsed_url))
931 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000932 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000933 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000934 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000935
936 def _ReadEntries(self):
937 """Read the .gclient_entries file for the given client.
938
939 Returns:
940 A sequence of solution names, which will be empty if there is the
941 entries file hasn't been created yet.
942 """
943 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000944 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000945 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000946 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000947 try:
948 exec(gclient_utils.FileRead(filename), scope)
949 except SyntaxError, e:
950 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000951 return scope['entries']
952
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000953 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000954 """Checks for revision overrides."""
955 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000956 if self._options.head:
957 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000958 # Do not check safesync_url if one or more --revision flag is specified.
959 if not self._options.revisions:
960 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000961 if not s.managed:
962 self._options.revisions.append('%s@unmanaged' % s.name)
963 elif s.safesync_url:
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000964 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000965 if not self._options.revisions:
966 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000967 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000968 index = 0
969 for revision in self._options.revisions:
970 if not '@' in revision:
971 # Support for --revision 123
972 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000973 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000974 if not sol in solutions_names:
975 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
976 print >> sys.stderr, ('Please fix your script, having invalid '
977 '--revision flags will soon considered an error.')
978 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000979 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000980 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000981 return revision_overrides
982
dbeam@chromium.org051c88b2011-12-22 00:23:03 +0000983 def _ApplySafeSyncRev(self, dep):
984 """Finds a valid revision from the content of the safesync_url and apply it
985 by appending revisions to the revision list. Throws if revision appears to
986 be invalid for the given |dep|."""
987 assert len(dep.safesync_url) > 0
988 handle = urllib.urlopen(dep.safesync_url)
989 rev = handle.read().strip()
990 handle.close()
991 if not rev:
992 raise gclient_utils.Error(
993 'It appears your safesync_url (%s) is not working properly\n'
994 '(as it returned an empty response). Check your config.' %
995 dep.safesync_url)
996 scm = gclient_scm.CreateSCM(dep.url, dep.root.root_dir, dep.name)
997 safe_rev = scm.GetUsableRev(rev=rev, options=self._options)
998 if self._options.verbose:
999 print('Using safesync_url revision: %s.\n' % safe_rev)
1000 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1001
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001002 def RunOnDeps(self, command, args):
1003 """Runs a command on each dependency in a client and its dependencies.
1004
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001005 Args:
1006 command: The command to use (e.g., 'status' or 'diff')
1007 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001008 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001009 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001010 raise gclient_utils.Error('No solution specified')
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001011 revision_overrides = {}
1012 # It's unnecessary to check for revision overrides for 'recurse'.
1013 # Save a few seconds by not calling _EnforceRevisions() in that case.
dbeam@chromium.org0f8a9442012-07-10 14:50:20 +00001014 if command not in ('diff', 'recurse', 'runhooks', 'status'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001015 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001016 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001017 # Disable progress for non-tty stdout.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001018 if (sys.stdout.isatty() and not self._options.verbose):
1019 if command in ('update', 'revert'):
1020 pm = Progress('Syncing projects', 1)
1021 elif command is 'recurse':
1022 pm = Progress(' '.join(args), 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001023 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001024 for s in self.dependencies:
1025 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001026 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +00001027
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001028 # Once all the dependencies have been processed, it's now safe to run the
1029 # hooks.
1030 if not self._options.nohooks:
1031 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001032
1033 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001034 # Notify the user if there is an orphaned entry in their working copy.
1035 # Only delete the directory if there are no changes in it, and
1036 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001037 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001038 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001039 if not prev_url:
1040 # entry must have been overridden via .gclient custom_deps
1041 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001042 # Fix path separator on Windows.
1043 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001044 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001045 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +00001046 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001047 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001048 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001049 scm.status(self._options, [], file_list)
1050 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001051 if (not self._options.delete_unversioned_trees or
1052 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001053 # There are modified files in this entry. Keep warning until
1054 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001055 print(('\nWARNING: \'%s\' is no longer part of this client. '
1056 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001057 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001058 else:
1059 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001060 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001061 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +00001062 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001063 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001064 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001065 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001066
1067 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001068 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001069 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001070 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001071 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001072 for s in self.dependencies:
1073 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001074 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001075
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001076 def GetURLAndRev(dep):
1077 """Returns the revision-qualified SCM url for a Dependency."""
1078 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001079 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001080 if isinstance(dep.parsed_url, self.FileImpl):
1081 original_url = dep.parsed_url.file_location
1082 else:
1083 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001084 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001085 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001086 if not os.path.isdir(scm.checkout_path):
1087 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001088 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001089
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001090 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001091 new_gclient = ''
1092 # First level at .gclient
1093 for d in self.dependencies:
1094 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001095 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001096 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001097 for d in dep.dependencies:
1098 entries[d.name] = GetURLAndRev(d)
1099 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001100 GrabDeps(d)
1101 custom_deps = []
1102 for k in sorted(entries.keys()):
1103 if entries[k]:
1104 # Quotes aren't escaped...
1105 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1106 else:
1107 custom_deps.append(' \"%s\": None,\n' % k)
1108 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1109 'solution_name': d.name,
1110 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001111 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001112 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001113 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001114 'solution_deps': ''.join(custom_deps),
1115 }
1116 # Print the snapshot configuration file
1117 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001118 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001119 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001120 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001121 if self._options.actual:
1122 entries[d.name] = GetURLAndRev(d)
1123 else:
1124 entries[d.name] = d.parsed_url
1125 keys = sorted(entries.keys())
1126 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001127 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001128 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001130 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001131 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001132 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001133
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001134 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001135 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001136 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001137 return self._root_dir
1138
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001139 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001140 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001141 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001142 return self._enforced_os
1143
maruel@chromium.org68988972011-09-20 14:11:42 +00001144 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001145 def recursion_limit(self):
1146 """How recursive can each dependencies in DEPS file can load DEPS file."""
1147 return self._recursion_limit
1148
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001149
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001150#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001151
1152
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001153def CMDcleanup(parser, args):
1154 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001155
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001156Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001157"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001158 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1159 help='override deps for the specified (comma-separated) '
1160 'platform(s); \'all\' will process all deps_os '
1161 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001162 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001163 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001164 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001165 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001166 if options.verbose:
1167 # Print out the .gclient file. This is longer than if we just printed the
1168 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001169 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001170 return client.RunOnDeps('cleanup', args)
1171
1172
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001173@attr('usage', '[command] [args ...]')
1174def CMDrecurse(parser, args):
1175 """Operates on all the entries.
1176
1177 Runs a shell command on all entries.
1178 """
1179 # Stop parsing at the first non-arg so that these go through to the command
1180 parser.disable_interspersed_args()
1181 parser.add_option('-s', '--scm', action='append', default=[],
1182 help='choose scm types to operate upon')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001183 parser.add_option('-i', '--ignore', action='store_true',
1184 help='continue processing in case of non zero return code')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001185 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001186 if not args:
1187 print >> sys.stderr, 'Need to supply a command!'
1188 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001189 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1190 if not root_and_entries:
1191 print >> sys.stderr, (
1192 'You need to run gclient sync at least once to use \'recurse\'.\n'
1193 'This is because .gclient_entries needs to exist and be up to date.')
1194 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001195
1196 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001197 scm_set = set()
1198 for scm in options.scm:
1199 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001200 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001201
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001202 options.nohooks = True
1203 client = GClient.LoadCurrentConfig(options)
1204 return client.RunOnDeps('recurse', args)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001205
1206
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001207@attr('usage', '[args ...]')
1208def CMDfetch(parser, args):
1209 """Fetches upstream commits for all modules.
1210
1211Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1212"""
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001213 (options, args) = parser.parse_args(args)
1214 args = ['-j%d' % options.jobs, '-s', 'git', 'git', 'fetch'] + args
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001215 return CMDrecurse(parser, args)
1216
1217
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001218@attr('usage', '[url] [safesync url]')
1219def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001220 """Create a .gclient file in the current directory.
1221
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001222This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001223top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001224modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001225provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001226URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001227"""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001228
1229 # We do a little dance with the --gclientfile option. 'gclient config' is the
1230 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1231 # arguments. So, we temporarily stash any --gclientfile parameter into
1232 # options.output_config_file until after the (gclientfile xor spec) error
1233 # check.
1234 parser.remove_option('--gclientfile')
1235 parser.add_option('--gclientfile', dest='output_config_file',
1236 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001237 parser.add_option('--name',
1238 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001239 parser.add_option('--deps-file', default='DEPS',
1240 help='overrides the default name for the DEPS file for the'
1241 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001242 parser.add_option('--unmanaged', action='store_true', default=False,
1243 help='overrides the default behavior to make it possible '
1244 'to have the main solution untouched by gclient '
1245 '(gclient will check out unmanaged dependencies but '
1246 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001247 parser.add_option('--git-deps', action='store_true',
1248 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001249 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001250 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001251 if options.output_config_file:
1252 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001253 if ((options.spec and args) or len(args) > 2 or
1254 (not options.spec and not args)):
1255 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1256
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001257 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001258 if options.spec:
1259 client.SetConfig(options.spec)
1260 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001261 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001262 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001263 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001264 if name.endswith('.git'):
1265 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001266 else:
1267 # specify an alternate relpath for the given URL.
1268 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001269 deps_file = options.deps_file
1270 if options.git_deps:
1271 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001272 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001273 if len(args) > 1:
1274 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001275 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1276 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001277 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001278 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001279
1280
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001281@attr('epilog', """Example:
1282 gclient pack > patch.txt
1283 generate simple patch for configured client and dependences
1284""")
1285def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001286 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001287
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001288Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001289dependencies, and performs minimal postprocessing of the output. The
1290resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001291checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001292"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001293 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1294 help='override deps for the specified (comma-separated) '
1295 'platform(s); \'all\' will process all deps_os '
1296 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001297 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001298 (options, args) = parser.parse_args(args)
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001299 # Force jobs to 1 so the stdout is not annotated with the thread ids
1300 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001301 client = GClient.LoadCurrentConfig(options)
1302 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001303 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001304 if options.verbose:
1305 # Print out the .gclient file. This is longer than if we just printed the
1306 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001307 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001308 return client.RunOnDeps('pack', args)
1309
1310
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001311def CMDstatus(parser, args):
1312 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001313 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1314 help='override deps for the specified (comma-separated) '
1315 'platform(s); \'all\' will process all deps_os '
1316 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001317 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001318 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001319 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001320 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001321 if options.verbose:
1322 # Print out the .gclient file. This is longer than if we just printed the
1323 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001324 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001325 return client.RunOnDeps('status', args)
1326
1327
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001328@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001329 gclient sync
1330 update files from SCM according to current configuration,
1331 *for modules which have changed since last update or sync*
1332 gclient sync --force
1333 update files from SCM according to current configuration, for
1334 all modules (useful for recovering files deleted from local copy)
1335 gclient sync --revision src@31000
1336 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001337""")
1338def CMDsync(parser, args):
1339 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001340 parser.add_option('-f', '--force', action='store_true',
1341 help='force update even for unchanged modules')
1342 parser.add_option('-n', '--nohooks', action='store_true',
1343 help='don\'t run hooks after the update is complete')
1344 parser.add_option('-r', '--revision', action='append',
1345 dest='revisions', metavar='REV', default=[],
1346 help='Enforces revision/hash for the solutions with the '
1347 'format src@rev. The src@ part is optional and can be '
1348 'skipped. -r can be used multiple times when .gclient '
1349 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001350 'if the src@ part is skipped. Note that specifying '
1351 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001352 parser.add_option('-t', '--transitive', action='store_true',
1353 help='When a revision is specified (in the DEPS file or '
1354 'with the command-line flag), transitively update '
1355 'the dependencies to the date of the given revision. '
1356 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001357 parser.add_option('-H', '--head', action='store_true',
1358 help='skips any safesync_urls specified in '
1359 'configured solutions and sync to head instead')
1360 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001361 help='Deletes from the working copy any dependencies that '
1362 'have been removed since the last sync, as long as '
1363 'there are no local modifications. When used with '
1364 '--force, such dependencies are removed even if they '
1365 'have local modifications. When used with --reset, '
1366 'all untracked directories are removed from the '
1367 'working copy, exclusing those which are explicitly '
1368 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001369 parser.add_option('-R', '--reset', action='store_true',
1370 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001371 parser.add_option('-M', '--merge', action='store_true',
1372 help='merge upstream changes instead of trying to '
1373 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001374 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1375 help='override deps for the specified (comma-separated) '
1376 'platform(s); \'all\' will process all deps_os '
1377 'references')
1378 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1379 help='Skip svn up whenever possible by requesting '
1380 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001381 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001382 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001383
1384 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001385 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001386
maruel@chromium.org307d1792010-05-31 20:03:13 +00001387 if options.revisions and options.head:
1388 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001389 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001390
1391 if options.verbose:
1392 # Print out the .gclient file. This is longer than if we just printed the
1393 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001394 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001395 return client.RunOnDeps('update', args)
1396
1397
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001398def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001399 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001400 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001401
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001402def CMDdiff(parser, args):
1403 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001404 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1405 help='override deps for the specified (comma-separated) '
1406 'platform(s); \'all\' will process all deps_os '
1407 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001408 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001409 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001410 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001411 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001412 if options.verbose:
1413 # Print out the .gclient file. This is longer than if we just printed the
1414 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001415 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001416 return client.RunOnDeps('diff', args)
1417
1418
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001419def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001420 """Revert all modifications in every dependencies.
1421
1422 That's the nuclear option to get back to a 'clean' state. It removes anything
1423 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001424 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1425 help='override deps for the specified (comma-separated) '
1426 'platform(s); \'all\' will process all deps_os '
1427 'references')
1428 parser.add_option('-n', '--nohooks', action='store_true',
1429 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001430 (options, args) = parser.parse_args(args)
1431 # --force is implied.
1432 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001433 options.reset = False
1434 options.delete_unversioned_trees = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001435 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001436 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001437 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001438 return client.RunOnDeps('revert', args)
1439
1440
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001441def CMDrunhooks(parser, args):
1442 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001443 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1444 help='override deps for the specified (comma-separated) '
1445 'platform(s); \'all\' will process all deps_os '
1446 'references')
1447 parser.add_option('-f', '--force', action='store_true', default=True,
1448 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001449 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001450 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001451 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001452 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001453 if options.verbose:
1454 # Print out the .gclient file. This is longer than if we just printed the
1455 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001456 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001457 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001458 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001459 return client.RunOnDeps('runhooks', args)
1460
1461
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001462def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001463 """Output revision info mapping for the client and its dependencies.
1464
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001465 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001466 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001467 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1468 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001469 commit can change.
1470 """
1471 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1472 help='override deps for the specified (comma-separated) '
1473 'platform(s); \'all\' will process all deps_os '
1474 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001475 parser.add_option('-a', '--actual', action='store_true',
1476 help='gets the actual checked out revisions instead of the '
1477 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001478 parser.add_option('-s', '--snapshot', action='store_true',
1479 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001480 'version of all repositories to reproduce the tree, '
1481 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001482 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001483 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001484 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001485 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001486 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001487 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001488
1489
szager@google.comb9a78d32012-03-13 18:46:21 +00001490def CMDhookinfo(parser, args):
1491 """Output the hooks that would be run by `gclient runhooks`"""
1492 (options, args) = parser.parse_args(args)
1493 options.force = True
1494 client = GClient.LoadCurrentConfig(options)
1495 if not client:
1496 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1497 client.RunOnDeps(None, [])
1498 print '; '.join(' '.join(hook) for hook in client.GetHooks(options))
1499 return 0
1500
1501
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001502def Command(name):
1503 return getattr(sys.modules[__name__], 'CMD' + name, None)
1504
1505
1506def CMDhelp(parser, args):
1507 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001508 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001509 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001510 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001511 parser.print_help()
1512 return 0
1513
1514
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001515def GenUsage(parser, command):
1516 """Modify an OptParse object with the function's documentation."""
1517 obj = Command(command)
1518 if command == 'help':
1519 command = '<command>'
1520 # OptParser.description prefer nicely non-formatted strings.
1521 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1522 usage = getattr(obj, 'usage', '')
1523 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1524 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001525
1526
maruel@chromium.org0895b752011-08-26 20:40:33 +00001527def Parser():
1528 """Returns the default parser."""
1529 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9aa1ce52012-07-16 13:57:18 +00001530 # some arm boards have issues with parallel sync.
1531 if platform.machine().startswith('arm'):
bradnelson@google.com4949dab2012-04-19 16:41:07 +00001532 jobs = 1
1533 else:
1534 jobs = 8
szager@chromium.orge2e03202012-07-31 18:05:16 +00001535 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
maruel@chromium.org41071612011-10-19 19:58:08 +00001536 parser.add_option('-j', '--jobs', default=jobs, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001537 help='Specify how many SCM commands can run in parallel; '
1538 'default=%default')
1539 parser.add_option('-v', '--verbose', action='count', default=0,
1540 help='Produces additional output for diagnostics. Can be '
1541 'used up to three times for more logging info.')
1542 parser.add_option('--gclientfile', dest='config_filename',
szager@chromium.orge2e03202012-07-31 18:05:16 +00001543 default=None,
1544 help='Specify an alternate %s file' % gclientfile_default)
1545 parser.add_option('--spec',
1546 default=None,
1547 help='create a gclient file containing the provided '
1548 'string. Due to Cygwin/Python brokenness, it '
1549 'probably can\'t contain any newlines.')
maruel@chromium.org0895b752011-08-26 20:40:33 +00001550 # Integrate standard options processing.
1551 old_parser = parser.parse_args
1552 def Parse(args):
1553 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001554 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1555 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001556 logging.basicConfig(level=level,
1557 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001558 if options.config_filename and options.spec:
1559 parser.error('Cannot specifiy both --gclientfile and --spec')
1560 if not options.config_filename:
1561 options.config_filename = gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00001562 options.entries_filename = options.config_filename + '_entries'
1563 if options.jobs < 1:
1564 parser.error('--jobs must be 1 or higher')
1565
1566 # These hacks need to die.
1567 if not hasattr(options, 'revisions'):
1568 # GClient.RunOnDeps expects it even if not applicable.
1569 options.revisions = []
1570 if not hasattr(options, 'head'):
1571 options.head = None
1572 if not hasattr(options, 'nohooks'):
1573 options.nohooks = True
1574 if not hasattr(options, 'deps_os'):
1575 options.deps_os = None
1576 if not hasattr(options, 'manually_grab_svn_rev'):
1577 options.manually_grab_svn_rev = None
1578 if not hasattr(options, 'force'):
1579 options.force = None
1580 return (options, args)
1581 parser.parse_args = Parse
1582 # We don't want wordwrapping in epilog (usually examples)
1583 parser.format_epilog = lambda _: parser.epilog or ''
1584 return parser
1585
1586
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001587def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001588 """Doesn't parse the arguments here, just find the right subcommand to
1589 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001590 if sys.hexversion < 0x02060000:
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001591 print >> sys.stderr, (
maruel@chromium.org82798cb2012-02-23 18:16:12 +00001592 '\nYour python version %s is unsupported, please upgrade.\n' %
1593 sys.version.split(' ', 1)[0])
1594 return 2
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001595 colorama.init()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001596 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001597 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1598 # operations. Python as a strong tendency to buffer sys.stdout.
1599 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001600 # Make stdout annotated with the thread ids.
1601 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001602 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001603 # Unused variable 'usage'
1604 # pylint: disable=W0612
maruel@chromium.orgda78c6f2011-10-23 00:13:58 +00001605 def to_str(fn):
1606 return (
1607 ' %s%-10s%s' % (Fore.GREEN, fn[3:], Fore.RESET) +
1608 ' %s' % Command(fn[3:]).__doc__.split('\n')[0].strip())
1609 cmds = (
1610 to_str(fn) for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')
1611 )
1612 CMDhelp.usage = '\n\nCommands are:\n' + '\n'.join(cmds)
maruel@chromium.org0895b752011-08-26 20:40:33 +00001613 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001614 if argv:
1615 command = Command(argv[0])
1616 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001617 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001618 GenUsage(parser, argv[0])
1619 return command(parser, argv[1:])
1620 # Not a known command. Default to help.
1621 GenUsage(parser, 'help')
1622 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001623 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001624 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001625 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001626
1627
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001628if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001629 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001630 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001631
1632# vim: ts=2:sw=2:tw=80:et: