blob: 22e5f990a635b2da696922bf41862c2fa6c1fc6d [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
2# Copyright (c) 2011 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 ]
50"""
51
phajdan.jr@chromium.org6e043f72011-05-02 07:24:32 +000052__version__ = "0.6.2"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000054import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000055import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056import optparse
57import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000058import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000059import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urllib
64
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000065import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066
maruel@chromium.org35625c72011-03-23 17:34:02 +000067import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000068import gclient_scm
69import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000070from third_party.repo.progress import Progress
maruel@chromium.org31cb48a2011-04-04 18:01:36 +000071import subprocess2
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000074def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000075 """Sets an attribute on a function."""
76 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000077 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000078 return fn
79 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082## GClient implementation.
83
84
maruel@chromium.org116704f2010-06-11 17:34:38 +000085class GClientKeywords(object):
86 class FromImpl(object):
87 """Used to implement the From() syntax."""
88
89 def __init__(self, module_name, sub_target_name=None):
90 """module_name is the dep module we want to include from. It can also be
91 the name of a subdirectory to include from.
92
93 sub_target_name is an optional parameter if the module name in the other
94 DEPS file is different. E.g., you might want to map src/net to net."""
95 self.module_name = module_name
96 self.sub_target_name = sub_target_name
97
98 def __str__(self):
99 return 'From(%s, %s)' % (repr(self.module_name),
100 repr(self.sub_target_name))
101
maruel@chromium.org116704f2010-06-11 17:34:38 +0000102 class FileImpl(object):
103 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000104 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000105
106 def __init__(self, file_location):
107 self.file_location = file_location
108
109 def __str__(self):
110 return 'File("%s")' % self.file_location
111
112 def GetPath(self):
113 return os.path.split(self.file_location)[0]
114
115 def GetFilename(self):
116 rev_tokens = self.file_location.split('@')
117 return os.path.split(rev_tokens[0])[1]
118
119 def GetRevision(self):
120 rev_tokens = self.file_location.split('@')
121 if len(rev_tokens) > 1:
122 return rev_tokens[1]
123 return None
124
125 class VarImpl(object):
126 def __init__(self, custom_vars, local_scope):
127 self._custom_vars = custom_vars
128 self._local_scope = local_scope
129
130 def Lookup(self, var_name):
131 """Implements the Var syntax."""
132 if var_name in self._custom_vars:
133 return self._custom_vars[var_name]
134 elif var_name in self._local_scope.get("vars", {}):
135 return self._local_scope["vars"][var_name]
136 raise gclient_utils.Error("Var is not defined: %s" % var_name)
137
138
maruel@chromium.org064186c2011-09-27 23:53:33 +0000139class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000140 """Immutable configuration settings."""
141 def __init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000142 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000143 deps_file, should_process):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000144 GClientKeywords.__init__(self)
145
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000146 # These are not mutable:
147 self._parent = parent
148 self._safesync_url = safesync_url
149 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000150 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000151 # 'managed' determines whether or not this dependency is synced/updated by
152 # gclient after gclient checks it out initially. The difference between
153 # 'managed' and 'should_process' is that the user specifies 'managed' via
154 # the --unmanaged command-line flag or a .gclient config, where
155 # 'should_process' is dynamically set by gclient if it goes over its
156 # recursion limit and controls gclient's behavior so it does not misbehave.
157 self._managed = managed
158 self._should_process = should_process
159
160 # These are only set in .gclient and not in DEPS files.
161 self._custom_vars = custom_vars or {}
162 self._custom_deps = custom_deps or {}
163
maruel@chromium.org064186c2011-09-27 23:53:33 +0000164 # Post process the url to remove trailing slashes.
165 if isinstance(self._url, basestring):
166 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
167 # it to proto://host/path@rev.
168 if self._url.count('@') > 1:
169 raise gclient_utils.Error('Invalid url "%s"' % self._url)
170 self._url = self._url.replace('/@', '@')
171 elif not isinstance(self._url,
172 (self.FromImpl, self.FileImpl, None.__class__)):
173 raise gclient_utils.Error(
174 ('dependency url must be either a string, None, '
175 'File() or From() instead of %s') % self._url.__class__.__name__)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000176 if '/' in self._deps_file or '\\' in self._deps_file:
177 raise gclient_utils.Error('deps_file name must not be a path, just a '
178 'filename. %s' % self._deps_file)
179
180 @property
181 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000182 return self._deps_file
183
184 @property
185 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000186 return self._managed
187
188 @property
189 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000190 return self._parent
191
192 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000193 def root(self):
194 """Returns the root node, a GClient object."""
195 if not self.parent:
196 # This line is to signal pylint that it could be a GClient instance.
197 return self or GClient(None, None)
198 return self.parent.root
199
200 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000201 def safesync_url(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202 return self._safesync_url
203
204 @property
205 def should_process(self):
206 """True if this dependency should be processed, i.e. checked out."""
207 return self._should_process
208
209 @property
210 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000211 return self._custom_vars.copy()
212
213 @property
214 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000215 return self._custom_deps.copy()
216
maruel@chromium.org064186c2011-09-27 23:53:33 +0000217 @property
218 def url(self):
219 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000220
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000221 @property
222 def recursion_limit(self):
223 """Returns > 0 if this dependency is not too recursed to be processed."""
224 return max(self.parent.recursion_limit - 1, 0)
225
226 def get_custom_deps(self, name, url):
227 """Returns a custom deps if applicable."""
228 if self.parent:
229 url = self.parent.get_custom_deps(name, url)
230 # None is a valid return value to disable a dependency.
231 return self.custom_deps.get(name, url)
232
maruel@chromium.org064186c2011-09-27 23:53:33 +0000233
234class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000235 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000236
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000237 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000238 custom_vars, deps_file, should_process):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000239 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 DependencySettings.__init__(
maruel@chromium.org064186c2011-09-27 23:53:33 +0000241 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 deps_file, should_process)
maruel@chromium.org68988972011-09-20 14:11:42 +0000243
244 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000245 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000246
247 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000249 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000250 # A cache of the files affected by the current operation, necessary for
251 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000252 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000253 # If it is not set to True, the dependency wasn't processed for its child
254 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000255 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000256 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000257 self._processed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000258 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000259 self._hooks_ran = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000260
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000261 if not self.name and self.parent:
262 raise gclient_utils.Error('Dependency without name')
263
264 def setup_requirements(self):
265 """Setup self.requirements and find any other dependency who would have self
266 as a requirement.
267
268 Returns True if this entry should be added, False if it is a duplicate of
269 another entry.
270 """
271 if self.name in [s.name for s in self.parent.dependencies]:
272 raise gclient_utils.Error(
273 'The same name "%s" appears multiple times in the deps section' %
274 self.name)
275 if self.should_process:
276 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
277 for sibling in siblings:
278 if self.url != sibling.url:
279 raise gclient_utils.Error(
280 'Dependency %s specified more than once:\n %s\nvs\n %s' %
281 (self.name, sibling.hierarchy(), self.hierarchy()))
282 # In theory we could keep it as a shadow of the other one. In
283 # practice, simply ignore it.
284 logging.warn('Won\'t process duplicate dependency %s' % sibling)
285 return False
maruel@chromium.org98023df2011-09-07 18:44:47 +0000286
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000287 # self.parent is implicitly a requirement. This will be recursive by
288 # definition.
289 if self.parent and self.parent.name:
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000290 self.add_requirement(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000291
292 # For a tree with at least 2 levels*, the leaf node needs to depend
293 # on the level higher up in an orderly way.
294 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
295 # thus unsorted, while the .gclient format is a list thus sorted.
296 #
297 # * _recursion_limit is hard coded 2 and there is no hope to change this
298 # value.
299 #
300 # Interestingly enough, the following condition only works in the case we
301 # want: self is a 2nd level node. 3nd level node wouldn't need this since
302 # they already have their parent as a requirement.
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000303 root_deps = self.root.dependencies
304 if self.parent in root_deps:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000305 for i in root_deps:
306 if i is self.parent:
307 break
308 if i.name:
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000309 self.add_requirement(i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000310
311 if isinstance(self.url, self.FromImpl):
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000312 self.add_requirement(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000313
maruel@chromium.org485dcab2011-09-14 12:48:47 +0000314 if self.name and self.should_process:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000315 for obj in self.root.depth_first_tree():
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000316 if obj is self or not obj.name:
317 continue
318 # Step 1: Find any requirements self may need.
319 if self.name.startswith(posixpath.join(obj.name, '')):
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000320 self.add_requirement(obj.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000321 # Step 2: Find any requirements self may impose.
322 if obj.name.startswith(posixpath.join(self.name, '')):
maruel@chromium.orga91d26d2011-10-05 00:03:06 +0000323 obj.add_requirement(self.name)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000324 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000325
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000326 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000327 """Resolves the parsed url from url.
328
329 Manages From() keyword accordingly. Do not touch self.parsed_url nor
330 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000331 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000332 parsed_url = self.get_custom_deps(self.name, url)
333 if parsed_url != url:
334 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url))
335 return parsed_url
336
337 if isinstance(url, self.FromImpl):
maruel@chromium.org68988972011-09-20 14:11:42 +0000338 ref = [
339 dep for dep in self.root.subtree(True) if url.module_name == dep.name
340 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000341 if not ref:
342 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
343 url.module_name, ref))
344 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000345 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000346 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000347 found_deps = [d for d in ref.dependencies if d.name == sub_target]
348 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000349 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000350 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
351 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000352 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000353
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000354 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000355 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000356 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000357 logging.info(
358 'LateOverride(%s, %s) -> %s (From)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000359 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000360
361 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362 parsed_url = urlparse.urlparse(url)
363 if not parsed_url[0]:
364 # A relative url. Fetch the real base.
365 path = parsed_url[2]
366 if not path.startswith('/'):
367 raise gclient_utils.Error(
368 'relative DEPS entry \'%s\' must begin with a slash' % url)
369 # Create a scm just to query the full url.
370 parent_url = self.parent.parsed_url
371 if isinstance(parent_url, self.FileImpl):
372 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000373 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000374 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000375 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000376 parsed_url = url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000377 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000378 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000379
380 if isinstance(url, self.FileImpl):
381 logging.info('LateOverride(%s, %s) -> %s (File)' % (self.name, url, url))
382 return url
383
384 if url is None:
385 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, url))
386 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000387
388 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000389
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000390 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000391 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000392 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000393 assert not self.dependencies
394 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000395 local_scope = {}
396 var = self.VarImpl(self.custom_vars, local_scope)
397 global_scope = {
398 'File': self.FileImpl,
399 'From': self.FromImpl,
400 'Var': var.Lookup,
401 'deps_os': {},
402 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000403 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000404 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000405 logging.info(
406 'ParseDepsFile(%s): No %s file found at %s' % (
407 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000408 else:
409 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000410 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000411 # Eval the content.
412 try:
413 exec(deps_content, global_scope, local_scope)
414 except SyntaxError, e:
415 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000416 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000417 # load os specific dependencies if defined. these dependencies may
418 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000419 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000420 enforced_os = self.root.enforced_os
421 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000422 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000423 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000424 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000425 # platform, so we collect the broadest set of dependencies
426 # available. We may end up with the wrong revision of something for
427 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000428 deps.update([x for x in os_deps.items() if not x[0] in deps])
429 else:
430 deps.update(os_deps)
431
maruel@chromium.org271375b2010-06-23 19:17:38 +0000432 # If a line is in custom_deps, but not in the solution, we want to append
433 # this line to the solution.
434 for d in self.custom_deps:
435 if d not in deps:
436 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000437
438 # If use_relative_paths is set in the DEPS file, regenerate
439 # the dictionary using paths relative to the directory containing
440 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000441 use_relative_paths = local_scope.get('use_relative_paths', False)
442 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000443 rel_deps = {}
444 for d, url in deps.items():
445 # normpath is required to allow DEPS to use .. in their
446 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000447 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
448 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000449
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000450 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000451 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000452 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000453 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000454 deps_to_add.append(Dependency(
455 self, name, url, None, None, None, None,
456 self.deps_file, should_process))
457 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
458 logging.info('ParseDepsFile(%s) done' % self.name)
459
460 def add_dependencies_and_close(self, deps_to_add, hooks):
461 """Adds the dependencies, hooks and mark the parsing as done."""
462 for dep in deps_to_add:
463 if dep.setup_requirements():
464 self.add_dependency(dep)
465 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000466
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000467 @staticmethod
468 def maybeGetParentRevision(
469 command, options, parsed_url, parent_name, revision_overrides):
470 """If we are performing an update and --transitive is set, set the
471 revision to the parent's revision. If we have an explicit revision
472 do nothing."""
473 if command == 'update' and options.transitive and not options.revision:
474 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
475 if not revision:
476 options.revision = revision_overrides.get(parent_name)
477 if options.verbose and options.revision:
478 print("Using parent's revision date: %s" % options.revision)
479 # If the parent has a revision override, then it must have been
480 # converted to date format.
481 assert (not options.revision or
482 gclient_utils.IsDateRevision(options.revision))
483
484 @staticmethod
485 def maybeConvertToDateRevision(
486 command, options, name, scm, revision_overrides):
487 """If we are performing an update and --transitive is set, convert the
488 revision to a date-revision (if necessary). Instead of having
489 -r 101 replace the revision with the time stamp of 101 (e.g.
490 "{2011-18-04}").
491 This way dependencies are upgraded to the revision they had at the
492 check-in of revision 101."""
493 if (command == 'update' and
494 options.transitive and
495 options.revision and
496 not gclient_utils.IsDateRevision(options.revision)):
497 revision_date = scm.GetRevisionDate(options.revision)
498 revision = gclient_utils.MakeDateRevision(revision_date)
499 if options.verbose:
500 print("Updating revision override from %s to %s." %
501 (options.revision, revision))
502 revision_overrides[name] = revision
503
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000504 # Arguments number differs from overridden method
505 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000506 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000507 """Runs |command| then parse the DEPS file."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000508 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000509 if not self.should_process:
510 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000511 # When running runhooks, there's no need to consult the SCM.
512 # All known hooks are expected to run unconditionally regardless of working
513 # copy state, so skip the SCM status check.
514 run_scm = command not in ('runhooks', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000515 parsed_url = self.LateOverride(self.url)
516 file_list = []
517 if run_scm and parsed_url:
518 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519 # Special support for single-file checkout.
520 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000521 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
522 # pylint: disable=E1103
523 options.revision = parsed_url.GetRevision()
524 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000525 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000526 self.name)
527 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000528 args + [parsed_url.GetFilename()],
529 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000530 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000531 # Create a shallow copy to mutate revision.
532 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000533 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000534 self.maybeGetParentRevision(
535 command, options, parsed_url, self.parent.name, revision_overrides)
536 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
537 scm.RunCommand(command, options, args, file_list)
538 self.maybeConvertToDateRevision(
539 command, options, self.name, scm, revision_overrides)
540 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000541
542 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
543 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000544 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000545 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000546 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000547 continue
548 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000549 [self.root.root_dir.lower(), file_list[i].lower()])
550 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000551 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000552 while file_list[i].startswith(('\\', '/')):
553 file_list[i] = file_list[i][1:]
554
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000555 # Always parse the DEPS file.
556 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000557
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000558 self._run_is_done(file_list, parsed_url)
559
560 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000561 # Parse the dependencies of this dependency.
562 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000563 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000564
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000565 @gclient_utils.lockedmethod
566 def _run_is_done(self, file_list, parsed_url):
567 # Both these are kept for hooks that are run as a separate tree traversal.
568 self._file_list = file_list
569 self._parsed_url = parsed_url
570 self._processed = True
571
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000572 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000573 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000574 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000575 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000576 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000577 # Don't run the hook when it is above recursion_limit.
578 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000579 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000580 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000581 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000582 # TODO(maruel): If the user is using git or git-svn, then we don't know
583 # what files have changed so we always run all hooks. It'd be nice to fix
584 # that.
585 if (options.force or
586 isinstance(self.parsed_url, self.FileImpl) or
587 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000588 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000589 for hook_dict in self.deps_hooks:
590 self._RunHookAction(hook_dict, [])
591 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000592 # Run hooks on the basis of whether the files from the gclient operation
593 # match each hook's pattern.
594 for hook_dict in self.deps_hooks:
595 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000596 matching_file_list = [
597 f for f in self.file_list_and_children if pattern.search(f)
598 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000599 if matching_file_list:
600 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000601 for s in self.dependencies:
602 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000603
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000604 def _RunHookAction(self, hook_dict, matching_file_list):
605 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000606 # A single DEPS file can specify multiple hooks so this function can be
607 # called multiple times on a single Dependency.
608 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000609 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000610 logging.debug(hook_dict)
611 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000612 command = hook_dict['action'][:]
613 if command[0] == 'python':
614 # If the hook specified "python" as the first item, the action is a
615 # Python script. Run it by starting a new copy of the same
616 # interpreter.
617 command[0] = sys.executable
618
619 if '$matching_files' in command:
620 splice_index = command.index('$matching_files')
621 command[splice_index:splice_index + 1] = matching_file_list
622
maruel@chromium.org17d01792010-09-01 18:07:10 +0000623 try:
624 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000625 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000626 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000627 # Use a discrete exit status code of 2 to indicate that a hook action
628 # failed. Users of this script may wish to treat hook action failures
629 # differently from VC failures.
630 print >> sys.stderr, 'Error: %s' % str(e)
631 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000632
maruel@chromium.org0d812442010-08-10 12:41:08 +0000633 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000634 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000635 dependencies = self.dependencies
636 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000637 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000638 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000639 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000640 for i in d.subtree(include_all):
641 yield i
642
643 def depth_first_tree(self):
644 """Depth-first recursion including the root node."""
645 yield self
646 for i in self.dependencies:
647 for j in i.depth_first_tree():
648 if j.should_process:
649 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000650
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000651 @gclient_utils.lockedmethod
652 def add_dependency(self, new_dep):
653 self._dependencies.append(new_dep)
654
655 @gclient_utils.lockedmethod
656 def _mark_as_parsed(self, new_hooks):
657 self._deps_hooks.extend(new_hooks)
658 self._deps_parsed = True
659
maruel@chromium.org68988972011-09-20 14:11:42 +0000660 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000661 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000662 def dependencies(self):
663 return tuple(self._dependencies)
664
665 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000666 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000667 def deps_hooks(self):
668 return tuple(self._deps_hooks)
669
670 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000671 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000672 def parsed_url(self):
673 return self._parsed_url
674
675 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000676 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000677 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000678 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000679 return self._deps_parsed
680
681 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000682 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000683 def processed(self):
684 return self._processed
685
686 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000687 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000688 def hooks_ran(self):
689 return self._hooks_ran
690
691 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000692 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000693 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000694 return tuple(self._file_list)
695
696 @property
697 def file_list_and_children(self):
698 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000699 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000700 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000701 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000702
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000703 def __str__(self):
704 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000705 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000706 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000707 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000708 # First try the native property if it exists.
709 if hasattr(self, '_' + i):
710 value = getattr(self, '_' + i, False)
711 else:
712 value = getattr(self, i, False)
713 if value:
714 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000715
716 for d in self.dependencies:
717 out.extend([' ' + x for x in str(d).splitlines()])
718 out.append('')
719 return '\n'.join(out)
720
721 def __repr__(self):
722 return '%s: %s' % (self.name, self.url)
723
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000724 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000725 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000726 out = '%s(%s)' % (self.name, self.url)
727 i = self.parent
728 while i and i.name:
729 out = '%s(%s) -> %s' % (i.name, i.url, out)
730 i = i.parent
731 return out
732
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000733
734class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000735 """Object that represent a gclient checkout. A tree of Dependency(), one per
736 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000737
738 DEPS_OS_CHOICES = {
739 "win32": "win",
740 "win": "win",
741 "cygwin": "win",
742 "darwin": "mac",
743 "mac": "mac",
744 "unix": "unix",
745 "linux": "unix",
746 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000747 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000748 }
749
750 DEFAULT_CLIENT_FILE_TEXT = ("""\
751solutions = [
752 { "name" : "%(solution_name)s",
753 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000754 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000755 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000756 "custom_deps" : {
757 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000758 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000759 },
760]
761""")
762
763 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
764 { "name" : "%(solution_name)s",
765 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000766 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000767 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000768 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000769%(solution_deps)s },
770 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000771 },
772""")
773
774 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
775# Snapshot generated with gclient revinfo --snapshot
776solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000777%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000778""")
779
780 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000781 # Do not change previous behavior. Only solution level and immediate DEPS
782 # are processed.
783 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000784 Dependency.__init__(self, None, None, None, None, True, None, None,
785 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000786 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000787 if options.deps_os:
788 enforced_os = options.deps_os.split(',')
789 else:
790 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
791 if 'all' in enforced_os:
792 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000793 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000794 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000795 self.config_content = None
796
797 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000798 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000799 config_dict = {}
800 self.config_content = content
801 try:
802 exec(content, config_dict)
803 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000804 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000805
806 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000807 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000808 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000809 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000810 self, s['name'], s['url'],
811 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000812 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000813 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000814 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000815 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000816 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000817 except KeyError:
818 raise gclient_utils.Error('Invalid .gclient file. Solution is '
819 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000820 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
821 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000822
823 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000824 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000825 self._options.config_filename),
826 self.config_content)
827
828 @staticmethod
829 def LoadCurrentConfig(options):
830 """Searches for and loads a .gclient file relative to the current working
831 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000832 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000833 if not path:
834 return None
835 client = GClient(path, options)
836 client.SetConfig(gclient_utils.FileRead(
837 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000838 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000839
nsylvain@google.comefc80932011-05-31 21:27:56 +0000840 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000841 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000842 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
843 'solution_name': solution_name,
844 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000845 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000846 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000847 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000848 })
849
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000850 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000851 """Creates a .gclient_entries file to record the list of unique checkouts.
852
853 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000854 """
855 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
856 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000857 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000858 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000859 # Skip over File() dependencies as we can't version them.
860 if not isinstance(entry.parsed_url, self.FileImpl):
861 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
862 pprint.pformat(entry.parsed_url))
863 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000864 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000865 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000866 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000867
868 def _ReadEntries(self):
869 """Read the .gclient_entries file for the given client.
870
871 Returns:
872 A sequence of solution names, which will be empty if there is the
873 entries file hasn't been created yet.
874 """
875 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000876 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000877 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000878 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000879 try:
880 exec(gclient_utils.FileRead(filename), scope)
881 except SyntaxError, e:
882 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000883 return scope['entries']
884
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000885 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000886 """Checks for revision overrides."""
887 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000888 if self._options.head:
889 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000890 # Do not check safesync_url if one or more --revision flag is specified.
891 if not self._options.revisions:
892 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000893 if not s.managed:
894 self._options.revisions.append('%s@unmanaged' % s.name)
895 elif s.safesync_url:
896 handle = urllib.urlopen(s.safesync_url)
897 rev = handle.read().strip()
898 handle.close()
899 if len(rev):
900 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000901 if not self._options.revisions:
902 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000903 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000904 index = 0
905 for revision in self._options.revisions:
906 if not '@' in revision:
907 # Support for --revision 123
908 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000909 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000910 if not sol in solutions_names:
911 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
912 print >> sys.stderr, ('Please fix your script, having invalid '
913 '--revision flags will soon considered an error.')
914 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000915 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000916 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000917 return revision_overrides
918
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000919 def RunOnDeps(self, command, args):
920 """Runs a command on each dependency in a client and its dependencies.
921
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000922 Args:
923 command: The command to use (e.g., 'status' or 'diff')
924 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000925 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000926 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000927 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000928 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000929 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000930 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000931 if (command in ('update', 'revert') and sys.stdout.isatty() and not
932 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000933 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000934 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000935 for s in self.dependencies:
936 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000937 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000938
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000939 # Once all the dependencies have been processed, it's now safe to run the
940 # hooks.
941 if not self._options.nohooks:
942 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000943
944 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000945 # Notify the user if there is an orphaned entry in their working copy.
946 # Only delete the directory if there are no changes in it, and
947 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000948 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000949 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000950 if not prev_url:
951 # entry must have been overridden via .gclient custom_deps
952 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000953 # Fix path separator on Windows.
954 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000955 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000956 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000957 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000958 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000959 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000960 scm.status(self._options, [], file_list)
961 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000962 if (not self._options.delete_unversioned_trees or
963 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000964 # There are modified files in this entry. Keep warning until
965 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000966 print(('\nWARNING: \'%s\' is no longer part of this client. '
967 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000968 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000969 else:
970 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000971 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000972 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000973 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000974 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000975 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000976 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000977
978 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000979 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000980 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000981 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000982 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000983 for s in self.dependencies:
984 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000985 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000986
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000987 def GetURLAndRev(dep):
988 """Returns the revision-qualified SCM url for a Dependency."""
989 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000990 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000991 if isinstance(dep.parsed_url, self.FileImpl):
992 original_url = dep.parsed_url.file_location
993 else:
994 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000995 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000996 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000997 if not os.path.isdir(scm.checkout_path):
998 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000999 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001000
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001001 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001002 new_gclient = ''
1003 # First level at .gclient
1004 for d in self.dependencies:
1005 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001006 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001007 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001008 for d in dep.dependencies:
1009 entries[d.name] = GetURLAndRev(d)
1010 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001011 GrabDeps(d)
1012 custom_deps = []
1013 for k in sorted(entries.keys()):
1014 if entries[k]:
1015 # Quotes aren't escaped...
1016 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1017 else:
1018 custom_deps.append(' \"%s\": None,\n' % k)
1019 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1020 'solution_name': d.name,
1021 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001022 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001023 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001024 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001025 'solution_deps': ''.join(custom_deps),
1026 }
1027 # Print the snapshot configuration file
1028 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001029 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001030 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001031 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001032 if self._options.actual:
1033 entries[d.name] = GetURLAndRev(d)
1034 else:
1035 entries[d.name] = d.parsed_url
1036 keys = sorted(entries.keys())
1037 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001038 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001039 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001041 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001042 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001043 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001044
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001045 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001046 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001047 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001048 return self._root_dir
1049
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001050 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001051 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001052 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001053 return self._enforced_os
1054
maruel@chromium.org68988972011-09-20 14:11:42 +00001055 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001056 def recursion_limit(self):
1057 """How recursive can each dependencies in DEPS file can load DEPS file."""
1058 return self._recursion_limit
1059
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001060
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001061#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001062
1063
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001064def CMDcleanup(parser, args):
1065 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001066
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001067Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001068"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001069 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1070 help='override deps for the specified (comma-separated) '
1071 'platform(s); \'all\' will process all deps_os '
1072 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001073 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001074 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001075 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001076 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001077 if options.verbose:
1078 # Print out the .gclient file. This is longer than if we just printed the
1079 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001080 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001081 return client.RunOnDeps('cleanup', args)
1082
1083
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001084@attr('usage', '[command] [args ...]')
1085def CMDrecurse(parser, args):
1086 """Operates on all the entries.
1087
1088 Runs a shell command on all entries.
1089 """
1090 # Stop parsing at the first non-arg so that these go through to the command
1091 parser.disable_interspersed_args()
1092 parser.add_option('-s', '--scm', action='append', default=[],
1093 help='choose scm types to operate upon')
1094 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001095 if not args:
1096 print >> sys.stderr, 'Need to supply a command!'
1097 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001098 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1099 if not root_and_entries:
1100 print >> sys.stderr, (
1101 'You need to run gclient sync at least once to use \'recurse\'.\n'
1102 'This is because .gclient_entries needs to exist and be up to date.')
1103 return 1
1104 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001105 scm_set = set()
1106 for scm in options.scm:
1107 scm_set.update(scm.split(','))
1108
1109 # Pass in the SCM type as an env variable
1110 env = os.environ.copy()
1111
1112 for path, url in entries.iteritems():
1113 scm = gclient_scm.GetScmName(url)
1114 if scm_set and scm not in scm_set:
1115 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001116 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001117 if scm:
1118 env['GCLIENT_SCM'] = scm
1119 if url:
1120 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001121 if os.path.isdir(cwd):
1122 subprocess2.call(args, cwd=cwd, env=env)
1123 else:
1124 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001125 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001126
1127
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001128@attr('usage', '[url] [safesync url]')
1129def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001130 """Create a .gclient file in the current directory.
1131
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001132This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001133top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001134modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001135provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001136URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001137"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001138 parser.add_option('--spec',
1139 help='create a gclient file containing the provided '
1140 'string. Due to Cygwin/Python brokenness, it '
1141 'probably can\'t contain any newlines.')
1142 parser.add_option('--name',
1143 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001144 parser.add_option('--deps-file', default='DEPS',
1145 help='overrides the default name for the DEPS file for the'
1146 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001147 parser.add_option('--unmanaged', action='store_true', default=False,
1148 help='overrides the default behavior to make it possible '
1149 'to have the main solution untouched by gclient '
1150 '(gclient will check out unmanaged dependencies but '
1151 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001152 parser.add_option('--git-deps', action='store_true',
1153 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001154 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001155 if ((options.spec and args) or len(args) > 2 or
1156 (not options.spec and not args)):
1157 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1158
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001159 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001160 if options.spec:
1161 client.SetConfig(options.spec)
1162 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001163 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001164 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001165 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001166 if name.endswith('.git'):
1167 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001168 else:
1169 # specify an alternate relpath for the given URL.
1170 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001171 deps_file = options.deps_file
1172 if options.git_deps:
1173 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001174 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001175 if len(args) > 1:
1176 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001177 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1178 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001179 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001180 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001181
1182
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001183@attr('epilog', """Example:
1184 gclient pack > patch.txt
1185 generate simple patch for configured client and dependences
1186""")
1187def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001188 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001189
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001190Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001191dependencies, and performs minimal postprocessing of the output. The
1192resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001193checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001194"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001195 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1196 help='override deps for the specified (comma-separated) '
1197 'platform(s); \'all\' will process all deps_os '
1198 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001199 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001200 client = GClient.LoadCurrentConfig(options)
1201 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001202 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001203 if options.verbose:
1204 # Print out the .gclient file. This is longer than if we just printed the
1205 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001206 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001207 return client.RunOnDeps('pack', args)
1208
1209
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001210def CMDstatus(parser, args):
1211 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001212 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1213 help='override deps for the specified (comma-separated) '
1214 'platform(s); \'all\' will process all deps_os '
1215 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001216 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001217 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001218 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001219 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001220 if options.verbose:
1221 # Print out the .gclient file. This is longer than if we just printed the
1222 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001223 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001224 return client.RunOnDeps('status', args)
1225
1226
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001227@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001228 gclient sync
1229 update files from SCM according to current configuration,
1230 *for modules which have changed since last update or sync*
1231 gclient sync --force
1232 update files from SCM according to current configuration, for
1233 all modules (useful for recovering files deleted from local copy)
1234 gclient sync --revision src@31000
1235 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001236""")
1237def CMDsync(parser, args):
1238 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001239 parser.add_option('-f', '--force', action='store_true',
1240 help='force update even for unchanged modules')
1241 parser.add_option('-n', '--nohooks', action='store_true',
1242 help='don\'t run hooks after the update is complete')
1243 parser.add_option('-r', '--revision', action='append',
1244 dest='revisions', metavar='REV', default=[],
1245 help='Enforces revision/hash for the solutions with the '
1246 'format src@rev. The src@ part is optional and can be '
1247 'skipped. -r can be used multiple times when .gclient '
1248 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001249 'if the src@ part is skipped. Note that specifying '
1250 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001251 parser.add_option('-t', '--transitive', action='store_true',
1252 help='When a revision is specified (in the DEPS file or '
1253 'with the command-line flag), transitively update '
1254 'the dependencies to the date of the given revision. '
1255 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001256 parser.add_option('-H', '--head', action='store_true',
1257 help='skips any safesync_urls specified in '
1258 'configured solutions and sync to head instead')
1259 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001260 help='delete any dependency that have been removed from '
1261 'last sync as long as there is no local modification. '
1262 'Coupled with --force, it will remove them even with '
1263 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001264 parser.add_option('-R', '--reset', action='store_true',
1265 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001266 parser.add_option('-M', '--merge', action='store_true',
1267 help='merge upstream changes instead of trying to '
1268 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001269 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1270 help='override deps for the specified (comma-separated) '
1271 'platform(s); \'all\' will process all deps_os '
1272 'references')
1273 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1274 help='Skip svn up whenever possible by requesting '
1275 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001276 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001277 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001278
1279 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001280 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001281
maruel@chromium.org307d1792010-05-31 20:03:13 +00001282 if options.revisions and options.head:
1283 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001284 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001285
1286 if options.verbose:
1287 # Print out the .gclient file. This is longer than if we just printed the
1288 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001289 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001290 return client.RunOnDeps('update', args)
1291
1292
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001293def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001294 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001295 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001296
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001297def CMDdiff(parser, args):
1298 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001299 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1300 help='override deps for the specified (comma-separated) '
1301 'platform(s); \'all\' will process all deps_os '
1302 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001303 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001304 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001305 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001306 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001307 if options.verbose:
1308 # Print out the .gclient file. This is longer than if we just printed the
1309 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001310 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001311 return client.RunOnDeps('diff', args)
1312
1313
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001314def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001315 """Revert all modifications in every dependencies.
1316
1317 That's the nuclear option to get back to a 'clean' state. It removes anything
1318 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001319 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1320 help='override deps for the specified (comma-separated) '
1321 'platform(s); \'all\' will process all deps_os '
1322 'references')
1323 parser.add_option('-n', '--nohooks', action='store_true',
1324 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001325 (options, args) = parser.parse_args(args)
1326 # --force is implied.
1327 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001328 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001329 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001330 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001331 return client.RunOnDeps('revert', args)
1332
1333
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001334def CMDrunhooks(parser, args):
1335 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001336 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1337 help='override deps for the specified (comma-separated) '
1338 'platform(s); \'all\' will process all deps_os '
1339 'references')
1340 parser.add_option('-f', '--force', action='store_true', default=True,
1341 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001342 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001343 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001344 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001345 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001346 if options.verbose:
1347 # Print out the .gclient file. This is longer than if we just printed the
1348 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001349 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001350 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001351 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001352 return client.RunOnDeps('runhooks', args)
1353
1354
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001355def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001356 """Output revision info mapping for the client and its dependencies.
1357
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001358 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001359 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001360 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1361 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001362 commit can change.
1363 """
1364 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1365 help='override deps for the specified (comma-separated) '
1366 'platform(s); \'all\' will process all deps_os '
1367 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001368 parser.add_option('-a', '--actual', action='store_true',
1369 help='gets the actual checked out revisions instead of the '
1370 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001371 parser.add_option('-s', '--snapshot', action='store_true',
1372 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001373 'version of all repositories to reproduce the tree, '
1374 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001375 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001376 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001377 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001378 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001379 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001380 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001381
1382
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001383def Command(name):
1384 return getattr(sys.modules[__name__], 'CMD' + name, None)
1385
1386
1387def CMDhelp(parser, args):
1388 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001389 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001390 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001391 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001392 parser.print_help()
1393 return 0
1394
1395
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001396def GenUsage(parser, command):
1397 """Modify an OptParse object with the function's documentation."""
1398 obj = Command(command)
1399 if command == 'help':
1400 command = '<command>'
1401 # OptParser.description prefer nicely non-formatted strings.
1402 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1403 usage = getattr(obj, 'usage', '')
1404 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1405 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001406
1407
maruel@chromium.org0895b752011-08-26 20:40:33 +00001408def Parser():
1409 """Returns the default parser."""
1410 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001411 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001412 help='Specify how many SCM commands can run in parallel; '
1413 'default=%default')
1414 parser.add_option('-v', '--verbose', action='count', default=0,
1415 help='Produces additional output for diagnostics. Can be '
1416 'used up to three times for more logging info.')
1417 parser.add_option('--gclientfile', dest='config_filename',
1418 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1419 help='Specify an alternate %default file')
1420 # Integrate standard options processing.
1421 old_parser = parser.parse_args
1422 def Parse(args):
1423 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001424 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1425 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001426 logging.basicConfig(level=level,
1427 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1428 options.entries_filename = options.config_filename + '_entries'
1429 if options.jobs < 1:
1430 parser.error('--jobs must be 1 or higher')
1431
1432 # These hacks need to die.
1433 if not hasattr(options, 'revisions'):
1434 # GClient.RunOnDeps expects it even if not applicable.
1435 options.revisions = []
1436 if not hasattr(options, 'head'):
1437 options.head = None
1438 if not hasattr(options, 'nohooks'):
1439 options.nohooks = True
1440 if not hasattr(options, 'deps_os'):
1441 options.deps_os = None
1442 if not hasattr(options, 'manually_grab_svn_rev'):
1443 options.manually_grab_svn_rev = None
1444 if not hasattr(options, 'force'):
1445 options.force = None
1446 return (options, args)
1447 parser.parse_args = Parse
1448 # We don't want wordwrapping in epilog (usually examples)
1449 parser.format_epilog = lambda _: parser.epilog or ''
1450 return parser
1451
1452
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001453def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001454 """Doesn't parse the arguments here, just find the right subcommand to
1455 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001456 if sys.hexversion < 0x02050000:
1457 print >> sys.stderr, (
1458 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001459 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001460 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1461 # operations. Python as a strong tendency to buffer sys.stdout.
1462 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001463 # Make stdout annotated with the thread ids.
1464 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001465 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001466 # Unused variable 'usage'
1467 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001468 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1469 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1470 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001471 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001472 if argv:
1473 command = Command(argv[0])
1474 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001475 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001476 GenUsage(parser, argv[0])
1477 return command(parser, argv[1:])
1478 # Not a known command. Default to help.
1479 GenUsage(parser, 'help')
1480 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001481 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001482 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001483 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001484
1485
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001486if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001487 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001488 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001489
1490# vim: ts=2:sw=2:tw=80:et: