blob: 6ecb941887330266baf2f15804d9b08b9d7ee484 [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.orgdf2b3152010-07-21 17:35:24 +0000347 # Make sure the referenced dependency DEPS file is loaded and file the
348 # inner referenced dependency.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000349 # TODO(maruel): Shouldn't do that.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000350 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000351 found_dep = None
352 for d in ref.dependencies:
353 if d.name == sub_target:
354 found_dep = d
355 break
356 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000357 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000358 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
359 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000360 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000361
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000363 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000364 logging.info(
365 'LateOverride(%s, %s) -> %s (From)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000366 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000367
368 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000369 parsed_url = urlparse.urlparse(url)
370 if not parsed_url[0]:
371 # A relative url. Fetch the real base.
372 path = parsed_url[2]
373 if not path.startswith('/'):
374 raise gclient_utils.Error(
375 'relative DEPS entry \'%s\' must begin with a slash' % url)
376 # Create a scm just to query the full url.
377 parent_url = self.parent.parsed_url
378 if isinstance(parent_url, self.FileImpl):
379 parent_url = parent_url.file_location
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000380 scm = gclient_scm.CreateSCM(parent_url, self.root.root_dir, None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000381 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000382 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000383 parsed_url = url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000384 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000385 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000386
387 if isinstance(url, self.FileImpl):
388 logging.info('LateOverride(%s, %s) -> %s (File)' % (self.name, url, url))
389 return url
390
391 if url is None:
392 logging.info('LateOverride(%s, %s) -> %s' % (self.name, url, url))
393 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000394
395 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000396
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000397 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000398 """Parses the DEPS file for this dependency."""
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000399 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000400 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000401 return
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000402 assert not self.dependencies
403 # One thing is unintuitive, vars = {} must happen before Var() use.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000404 local_scope = {}
405 var = self.VarImpl(self.custom_vars, local_scope)
406 global_scope = {
407 'File': self.FileImpl,
408 'From': self.FromImpl,
409 'Var': var.Lookup,
410 'deps_os': {},
411 }
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000412 filepath = os.path.join(self.root.root_dir, self.name, self.deps_file)
maruel@chromium.org46304292010-10-28 11:42:00 +0000413 if not os.path.isfile(filepath):
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000414 logging.info(
415 'ParseDepsFile(%s): No %s file found at %s' % (
416 self.name, self.deps_file, filepath))
maruel@chromium.org46304292010-10-28 11:42:00 +0000417 else:
418 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000419 logging.debug('ParseDepsFile(%s) read:\n%s' % (self.name, deps_content))
maruel@chromium.org46304292010-10-28 11:42:00 +0000420 # Eval the content.
421 try:
422 exec(deps_content, global_scope, local_scope)
423 except SyntaxError, e:
424 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000425 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000426 # load os specific dependencies if defined. these dependencies may
427 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000428 if 'deps_os' in local_scope:
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000429 enforced_os = self.root.enforced_os
430 for deps_os_key in enforced_os:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000431 os_deps = local_scope['deps_os'].get(deps_os_key, {})
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000432 if len(enforced_os) > 1:
maruel@chromium.org271375b2010-06-23 19:17:38 +0000433 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000434 # platform, so we collect the broadest set of dependencies
435 # available. We may end up with the wrong revision of something for
436 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000437 deps.update([x for x in os_deps.items() if not x[0] in deps])
438 else:
439 deps.update(os_deps)
440
maruel@chromium.org271375b2010-06-23 19:17:38 +0000441 # If a line is in custom_deps, but not in the solution, we want to append
442 # this line to the solution.
443 for d in self.custom_deps:
444 if d not in deps:
445 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000446
447 # If use_relative_paths is set in the DEPS file, regenerate
448 # the dictionary using paths relative to the directory containing
449 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000450 use_relative_paths = local_scope.get('use_relative_paths', False)
451 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000452 rel_deps = {}
453 for d, url in deps.items():
454 # normpath is required to allow DEPS to use .. in their
455 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000456 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
457 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000458
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000459 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000460 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000461 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000462 should_process = self.recursion_limit and self.should_process
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000463 deps_to_add.append(Dependency(
464 self, name, url, None, None, None, None,
465 self.deps_file, should_process))
466 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', []))
467 logging.info('ParseDepsFile(%s) done' % self.name)
468
469 def add_dependencies_and_close(self, deps_to_add, hooks):
470 """Adds the dependencies, hooks and mark the parsing as done."""
471 for dep in deps_to_add:
472 if dep.setup_requirements():
473 self.add_dependency(dep)
474 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000475
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000476 @staticmethod
477 def maybeGetParentRevision(
478 command, options, parsed_url, parent_name, revision_overrides):
479 """If we are performing an update and --transitive is set, set the
480 revision to the parent's revision. If we have an explicit revision
481 do nothing."""
482 if command == 'update' and options.transitive and not options.revision:
483 _, revision = gclient_utils.SplitUrlRevision(parsed_url)
484 if not revision:
485 options.revision = revision_overrides.get(parent_name)
486 if options.verbose and options.revision:
487 print("Using parent's revision date: %s" % options.revision)
488 # If the parent has a revision override, then it must have been
489 # converted to date format.
490 assert (not options.revision or
491 gclient_utils.IsDateRevision(options.revision))
492
493 @staticmethod
494 def maybeConvertToDateRevision(
495 command, options, name, scm, revision_overrides):
496 """If we are performing an update and --transitive is set, convert the
497 revision to a date-revision (if necessary). Instead of having
498 -r 101 replace the revision with the time stamp of 101 (e.g.
499 "{2011-18-04}").
500 This way dependencies are upgraded to the revision they had at the
501 check-in of revision 101."""
502 if (command == 'update' and
503 options.transitive and
504 options.revision and
505 not gclient_utils.IsDateRevision(options.revision)):
506 revision_date = scm.GetRevisionDate(options.revision)
507 revision = gclient_utils.MakeDateRevision(revision_date)
508 if options.verbose:
509 print("Updating revision override from %s to %s." %
510 (options.revision, revision))
511 revision_overrides[name] = revision
512
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000513 # Arguments number differs from overridden method
514 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000515 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000516 """Runs |command| then parse the DEPS file."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000517 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000518 if not self.should_process:
519 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000520 # When running runhooks, there's no need to consult the SCM.
521 # All known hooks are expected to run unconditionally regardless of working
522 # copy state, so skip the SCM status check.
523 run_scm = command not in ('runhooks', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000524 parsed_url = self.LateOverride(self.url)
525 file_list = []
526 if run_scm and parsed_url:
527 if isinstance(parsed_url, self.FileImpl):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000528 # Special support for single-file checkout.
529 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000530 # Sadly, pylint doesn't realize that parsed_url is of FileImpl.
531 # pylint: disable=E1103
532 options.revision = parsed_url.GetRevision()
533 scm = gclient_scm.SVNWrapper(parsed_url.GetPath(),
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000534 self.root.root_dir,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000535 self.name)
536 scm.RunCommand('updatesingle', options,
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000537 args + [parsed_url.GetFilename()],
538 file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000539 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000540 # Create a shallow copy to mutate revision.
541 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000542 options.revision = revision_overrides.get(self.name)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000543 self.maybeGetParentRevision(
544 command, options, parsed_url, self.parent.name, revision_overrides)
545 scm = gclient_scm.CreateSCM(parsed_url, self.root.root_dir, self.name)
546 scm.RunCommand(command, options, args, file_list)
547 self.maybeConvertToDateRevision(
548 command, options, self.name, scm, revision_overrides)
549 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000550
551 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
552 # Convert all absolute paths to relative.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000553 for i in range(len(file_list)):
maruel@chromium.org68988972011-09-20 14:11:42 +0000554 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000555 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000556 continue
557 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000558 [self.root.root_dir.lower(), file_list[i].lower()])
559 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000560 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000561 while file_list[i].startswith(('\\', '/')):
562 file_list[i] = file_list[i][1:]
563
maruel@chromium.org68988972011-09-20 14:11:42 +0000564 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000565 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000566 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000567
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000568 self._run_is_done(file_list, parsed_url)
569
570 if self.recursion_limit:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000571 # Parse the dependencies of this dependency.
572 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000573 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000574
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000575 @gclient_utils.lockedmethod
576 def _run_is_done(self, file_list, parsed_url):
577 # Both these are kept for hooks that are run as a separate tree traversal.
578 self._file_list = file_list
579 self._parsed_url = parsed_url
580 self._processed = True
581
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000582 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000583 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000584 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000585 assert self.hooks_ran == False
maruel@chromium.org68988972011-09-20 14:11:42 +0000586 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000587 # Don't run the hook when it is above recursion_limit.
588 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000589 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000590 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000591 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000592 # TODO(maruel): If the user is using git or git-svn, then we don't know
593 # what files have changed so we always run all hooks. It'd be nice to fix
594 # that.
595 if (options.force or
596 isinstance(self.parsed_url, self.FileImpl) or
597 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000598 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000599 for hook_dict in self.deps_hooks:
600 self._RunHookAction(hook_dict, [])
601 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000602 # Run hooks on the basis of whether the files from the gclient operation
603 # match each hook's pattern.
604 for hook_dict in self.deps_hooks:
605 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000606 matching_file_list = [
607 f for f in self.file_list_and_children if pattern.search(f)
608 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000609 if matching_file_list:
610 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000611 for s in self.dependencies:
612 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000613
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000614 def _RunHookAction(self, hook_dict, matching_file_list):
615 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000616 # A single DEPS file can specify multiple hooks so this function can be
617 # called multiple times on a single Dependency.
618 #assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000619 self._hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000620 logging.debug(hook_dict)
621 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000622 command = hook_dict['action'][:]
623 if command[0] == 'python':
624 # If the hook specified "python" as the first item, the action is a
625 # Python script. Run it by starting a new copy of the same
626 # interpreter.
627 command[0] = sys.executable
628
629 if '$matching_files' in command:
630 splice_index = command.index('$matching_files')
631 command[splice_index:splice_index + 1] = matching_file_list
632
maruel@chromium.org17d01792010-09-01 18:07:10 +0000633 try:
634 gclient_utils.CheckCallAndFilterAndHeader(
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000635 command, cwd=self.root.root_dir, always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000636 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000637 # Use a discrete exit status code of 2 to indicate that a hook action
638 # failed. Users of this script may wish to treat hook action failures
639 # differently from VC failures.
640 print >> sys.stderr, 'Error: %s' % str(e)
641 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000642
maruel@chromium.org0d812442010-08-10 12:41:08 +0000643 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000644 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000645 dependencies = self.dependencies
646 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000647 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000648 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000649 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000650 for i in d.subtree(include_all):
651 yield i
652
653 def depth_first_tree(self):
654 """Depth-first recursion including the root node."""
655 yield self
656 for i in self.dependencies:
657 for j in i.depth_first_tree():
658 if j.should_process:
659 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000660
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000661 @gclient_utils.lockedmethod
662 def add_dependency(self, new_dep):
663 self._dependencies.append(new_dep)
664
665 @gclient_utils.lockedmethod
666 def _mark_as_parsed(self, new_hooks):
667 self._deps_hooks.extend(new_hooks)
668 self._deps_parsed = True
669
maruel@chromium.org68988972011-09-20 14:11:42 +0000670 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000671 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000672 def dependencies(self):
673 return tuple(self._dependencies)
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_hooks(self):
678 return tuple(self._deps_hooks)
679
680 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000681 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000682 def parsed_url(self):
683 return self._parsed_url
684
685 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000686 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000687 def deps_parsed(self):
688 return self._deps_parsed
689
690 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000691 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000692 def processed(self):
693 return self._processed
694
695 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000696 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000697 def hooks_ran(self):
698 return self._hooks_ran
699
700 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000701 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000702 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000703 return tuple(self._file_list)
704
705 @property
706 def file_list_and_children(self):
707 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000708 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000709 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +0000710 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000711
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000712 def __str__(self):
713 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000714 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000715 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000716 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +0000717 # First try the native property if it exists.
718 if hasattr(self, '_' + i):
719 value = getattr(self, '_' + i, False)
720 else:
721 value = getattr(self, i, False)
722 if value:
723 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000724
725 for d in self.dependencies:
726 out.extend([' ' + x for x in str(d).splitlines()])
727 out.append('')
728 return '\n'.join(out)
729
730 def __repr__(self):
731 return '%s: %s' % (self.name, self.url)
732
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000733 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000734 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000735 out = '%s(%s)' % (self.name, self.url)
736 i = self.parent
737 while i and i.name:
738 out = '%s(%s) -> %s' % (i.name, i.url, out)
739 i = i.parent
740 return out
741
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000742
743class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000744 """Object that represent a gclient checkout. A tree of Dependency(), one per
745 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000746
747 DEPS_OS_CHOICES = {
748 "win32": "win",
749 "win": "win",
750 "cygwin": "win",
751 "darwin": "mac",
752 "mac": "mac",
753 "unix": "unix",
754 "linux": "unix",
755 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +0000756 "linux3": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000757 }
758
759 DEFAULT_CLIENT_FILE_TEXT = ("""\
760solutions = [
761 { "name" : "%(solution_name)s",
762 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000763 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000764 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000765 "custom_deps" : {
766 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000767 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000768 },
769]
770""")
771
772 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
773 { "name" : "%(solution_name)s",
774 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +0000775 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000776 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000777 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000778%(solution_deps)s },
779 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000780 },
781""")
782
783 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
784# Snapshot generated with gclient revinfo --snapshot
785solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000786%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000787""")
788
789 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000790 # Do not change previous behavior. Only solution level and immediate DEPS
791 # are processed.
792 self._recursion_limit = 2
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000793 Dependency.__init__(self, None, None, None, None, True, None, None,
794 'unused', True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000795 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000796 if options.deps_os:
797 enforced_os = options.deps_os.split(',')
798 else:
799 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
800 if 'all' in enforced_os:
801 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000802 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +0000803 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000804 self.config_content = None
805
806 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000807 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000808 config_dict = {}
809 self.config_content = content
810 try:
811 exec(content, config_dict)
812 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000813 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000814
815 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000816 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000817 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000818 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +0000819 self, s['name'], s['url'],
820 s.get('safesync_url', None),
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000821 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +0000822 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000823 s.get('custom_vars', {}),
nsylvain@google.comefc80932011-05-31 21:27:56 +0000824 s.get('deps_file', 'DEPS'),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000825 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000826 except KeyError:
827 raise gclient_utils.Error('Invalid .gclient file. Solution is '
828 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000829 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
830 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000831
832 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000833 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000834 self._options.config_filename),
835 self.config_content)
836
837 @staticmethod
838 def LoadCurrentConfig(options):
839 """Searches for and loads a .gclient file relative to the current working
840 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000841 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000842 if not path:
843 return None
844 client = GClient(path, options)
845 client.SetConfig(gclient_utils.FileRead(
846 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000847 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000848
nsylvain@google.comefc80932011-05-31 21:27:56 +0000849 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000850 safesync_url, managed=True):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000851 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
852 'solution_name': solution_name,
853 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +0000854 'deps_file': deps_file,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000855 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000856 'managed': managed,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000857 })
858
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000859 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000860 """Creates a .gclient_entries file to record the list of unique checkouts.
861
862 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000863 """
864 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
865 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000866 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +0000867 for entry in self.root.subtree(False):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000868 # Skip over File() dependencies as we can't version them.
869 if not isinstance(entry.parsed_url, self.FileImpl):
870 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
871 pprint.pformat(entry.parsed_url))
872 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000873 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000874 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000875 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000876
877 def _ReadEntries(self):
878 """Read the .gclient_entries file for the given client.
879
880 Returns:
881 A sequence of solution names, which will be empty if there is the
882 entries file hasn't been created yet.
883 """
884 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000885 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000886 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000887 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000888 try:
889 exec(gclient_utils.FileRead(filename), scope)
890 except SyntaxError, e:
891 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000892 return scope['entries']
893
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000894 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000895 """Checks for revision overrides."""
896 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000897 if self._options.head:
898 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000899 # Do not check safesync_url if one or more --revision flag is specified.
900 if not self._options.revisions:
901 for s in self.dependencies:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +0000902 if not s.managed:
903 self._options.revisions.append('%s@unmanaged' % s.name)
904 elif s.safesync_url:
905 handle = urllib.urlopen(s.safesync_url)
906 rev = handle.read().strip()
907 handle.close()
908 if len(rev):
909 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000910 if not self._options.revisions:
911 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000912 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000913 index = 0
914 for revision in self._options.revisions:
915 if not '@' in revision:
916 # Support for --revision 123
917 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000918 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000919 if not sol in solutions_names:
920 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
921 print >> sys.stderr, ('Please fix your script, having invalid '
922 '--revision flags will soon considered an error.')
923 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000924 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000925 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000926 return revision_overrides
927
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000928 def RunOnDeps(self, command, args):
929 """Runs a command on each dependency in a client and its dependencies.
930
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000931 Args:
932 command: The command to use (e.g., 'status' or 'diff')
933 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000934 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000935 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000936 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000937 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000938 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000939 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000940 if (command in ('update', 'revert') and sys.stdout.isatty() and not
941 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000942 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000943 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000944 for s in self.dependencies:
945 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000946 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000947
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000948 # Once all the dependencies have been processed, it's now safe to run the
949 # hooks.
950 if not self._options.nohooks:
951 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000952
953 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000954 # Notify the user if there is an orphaned entry in their working copy.
955 # Only delete the directory if there are no changes in it, and
956 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +0000957 entries = [i.name for i in self.root.subtree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000958 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000959 if not prev_url:
960 # entry must have been overridden via .gclient custom_deps
961 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000962 # Fix path separator on Windows.
963 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000964 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000965 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000966 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000967 file_list = []
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000968 scm = gclient_scm.CreateSCM(prev_url, self.root_dir, entry_fixed)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000969 scm.status(self._options, [], file_list)
970 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000971 if (not self._options.delete_unversioned_trees or
972 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000973 # There are modified files in this entry. Keep warning until
974 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000975 print(('\nWARNING: \'%s\' is no longer part of this client. '
976 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000977 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000978 else:
979 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000980 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000981 entry_fixed, self.root_dir))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000982 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000983 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000984 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000985 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000986
987 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000988 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000989 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000990 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000991 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000992 for s in self.dependencies:
993 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000994 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000995
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000996 def GetURLAndRev(dep):
997 """Returns the revision-qualified SCM url for a Dependency."""
998 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000999 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001000 if isinstance(dep.parsed_url, self.FileImpl):
1001 original_url = dep.parsed_url.file_location
1002 else:
1003 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +00001004 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001005 scm = gclient_scm.CreateSCM(original_url, self.root_dir, dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001006 if not os.path.isdir(scm.checkout_path):
1007 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001008 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001009
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001010 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001011 new_gclient = ''
1012 # First level at .gclient
1013 for d in self.dependencies:
1014 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001015 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001016 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001017 for d in dep.dependencies:
1018 entries[d.name] = GetURLAndRev(d)
1019 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001020 GrabDeps(d)
1021 custom_deps = []
1022 for k in sorted(entries.keys()):
1023 if entries[k]:
1024 # Quotes aren't escaped...
1025 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1026 else:
1027 custom_deps.append(' \"%s\": None,\n' % k)
1028 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1029 'solution_name': d.name,
1030 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001031 'deps_file': d.deps_file,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001032 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001033 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001034 'solution_deps': ''.join(custom_deps),
1035 }
1036 # Print the snapshot configuration file
1037 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001038 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001039 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001040 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001041 if self._options.actual:
1042 entries[d.name] = GetURLAndRev(d)
1043 else:
1044 entries[d.name] = d.parsed_url
1045 keys = sorted(entries.keys())
1046 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001047 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001048 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001050 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001051 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001052 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001053
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001054 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001055 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001056 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001057 return self._root_dir
1058
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001059 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001060 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001061 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001062 return self._enforced_os
1063
maruel@chromium.org68988972011-09-20 14:11:42 +00001064 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001065 def recursion_limit(self):
1066 """How recursive can each dependencies in DEPS file can load DEPS file."""
1067 return self._recursion_limit
1068
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001069
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001070#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001071
1072
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001073def CMDcleanup(parser, args):
1074 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001075
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001076Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001077"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001078 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1079 help='override deps for the specified (comma-separated) '
1080 'platform(s); \'all\' will process all deps_os '
1081 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001082 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001083 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001084 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001085 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001086 if options.verbose:
1087 # Print out the .gclient file. This is longer than if we just printed the
1088 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001089 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001090 return client.RunOnDeps('cleanup', args)
1091
1092
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001093@attr('usage', '[command] [args ...]')
1094def CMDrecurse(parser, args):
1095 """Operates on all the entries.
1096
1097 Runs a shell command on all entries.
1098 """
1099 # Stop parsing at the first non-arg so that these go through to the command
1100 parser.disable_interspersed_args()
1101 parser.add_option('-s', '--scm', action='append', default=[],
1102 help='choose scm types to operate upon')
1103 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001104 if not args:
1105 print >> sys.stderr, 'Need to supply a command!'
1106 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001107 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1108 if not root_and_entries:
1109 print >> sys.stderr, (
1110 'You need to run gclient sync at least once to use \'recurse\'.\n'
1111 'This is because .gclient_entries needs to exist and be up to date.')
1112 return 1
1113 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001114 scm_set = set()
1115 for scm in options.scm:
1116 scm_set.update(scm.split(','))
1117
1118 # Pass in the SCM type as an env variable
1119 env = os.environ.copy()
1120
1121 for path, url in entries.iteritems():
1122 scm = gclient_scm.GetScmName(url)
1123 if scm_set and scm not in scm_set:
1124 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +00001125 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +00001126 if scm:
1127 env['GCLIENT_SCM'] = scm
1128 if url:
1129 env['GCLIENT_URL'] = url
maruel@chromium.org4a271d52011-09-30 19:56:53 +00001130 if os.path.isdir(cwd):
1131 subprocess2.call(args, cwd=cwd, env=env)
1132 else:
1133 print >> sys.stderr, 'Skipped missing %s' % cwd
maruel@chromium.orgac610232010-10-13 14:01:31 +00001134 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001135
1136
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001137@attr('usage', '[url] [safesync url]')
1138def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001139 """Create a .gclient file in the current directory.
1140
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001141This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +00001142top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001143modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +00001144provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001145URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001146"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001147 parser.add_option('--spec',
1148 help='create a gclient file containing the provided '
1149 'string. Due to Cygwin/Python brokenness, it '
1150 'probably can\'t contain any newlines.')
1151 parser.add_option('--name',
1152 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001153 parser.add_option('--deps-file', default='DEPS',
1154 help='overrides the default name for the DEPS file for the'
1155 'main solutions and all sub-dependencies')
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001156 parser.add_option('--unmanaged', action='store_true', default=False,
1157 help='overrides the default behavior to make it possible '
1158 'to have the main solution untouched by gclient '
1159 '(gclient will check out unmanaged dependencies but '
1160 'will never sync them)')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001161 parser.add_option('--git-deps', action='store_true',
1162 help='sets the deps file to ".DEPS.git" instead of "DEPS"')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001163 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001164 if ((options.spec and args) or len(args) > 2 or
1165 (not options.spec and not args)):
1166 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1167
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001168 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001169 if options.spec:
1170 client.SetConfig(options.spec)
1171 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001172 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001173 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001174 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001175 if name.endswith('.git'):
1176 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001177 else:
1178 # specify an alternate relpath for the given URL.
1179 name = options.name
nsylvain@google.comefc80932011-05-31 21:27:56 +00001180 deps_file = options.deps_file
1181 if options.git_deps:
1182 deps_file = '.DEPS.git'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001183 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001184 if len(args) > 1:
1185 safesync_url = args[1]
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001186 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1187 managed=not options.unmanaged)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001189 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001190
1191
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001192@attr('epilog', """Example:
1193 gclient pack > patch.txt
1194 generate simple patch for configured client and dependences
1195""")
1196def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +00001197 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001198
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001199Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +00001200dependencies, and performs minimal postprocessing of the output. The
1201resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001202checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +00001203"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001204 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1205 help='override deps for the specified (comma-separated) '
1206 'platform(s); \'all\' will process all deps_os '
1207 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001208 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +00001209 client = GClient.LoadCurrentConfig(options)
1210 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001211 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001212 if options.verbose:
1213 # Print out the .gclient file. This is longer than if we just printed the
1214 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001215 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001216 return client.RunOnDeps('pack', args)
1217
1218
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001219def CMDstatus(parser, args):
1220 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001221 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1222 help='override deps for the specified (comma-separated) '
1223 'platform(s); \'all\' will process all deps_os '
1224 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001225 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001226 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001227 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001228 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001229 if options.verbose:
1230 # Print out the .gclient file. This is longer than if we just printed the
1231 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001232 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001233 return client.RunOnDeps('status', args)
1234
1235
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001236@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001237 gclient sync
1238 update files from SCM according to current configuration,
1239 *for modules which have changed since last update or sync*
1240 gclient sync --force
1241 update files from SCM according to current configuration, for
1242 all modules (useful for recovering files deleted from local copy)
1243 gclient sync --revision src@31000
1244 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001245""")
1246def CMDsync(parser, args):
1247 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001248 parser.add_option('-f', '--force', action='store_true',
1249 help='force update even for unchanged modules')
1250 parser.add_option('-n', '--nohooks', action='store_true',
1251 help='don\'t run hooks after the update is complete')
1252 parser.add_option('-r', '--revision', action='append',
1253 dest='revisions', metavar='REV', default=[],
1254 help='Enforces revision/hash for the solutions with the '
1255 'format src@rev. The src@ part is optional and can be '
1256 'skipped. -r can be used multiple times when .gclient '
1257 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001258 'if the src@ part is skipped. Note that specifying '
1259 '--revision means your safesync_url gets ignored.')
floitsch@google.comeaab7842011-04-28 09:07:58 +00001260 parser.add_option('-t', '--transitive', action='store_true',
1261 help='When a revision is specified (in the DEPS file or '
1262 'with the command-line flag), transitively update '
1263 'the dependencies to the date of the given revision. '
1264 'Only supported for SVN repositories.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001265 parser.add_option('-H', '--head', action='store_true',
1266 help='skips any safesync_urls specified in '
1267 'configured solutions and sync to head instead')
1268 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001269 help='delete any dependency that have been removed from '
1270 'last sync as long as there is no local modification. '
1271 'Coupled with --force, it will remove them even with '
1272 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001273 parser.add_option('-R', '--reset', action='store_true',
1274 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001275 parser.add_option('-M', '--merge', action='store_true',
1276 help='merge upstream changes instead of trying to '
1277 'fast-forward or rebase')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001278 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1279 help='override deps for the specified (comma-separated) '
1280 'platform(s); \'all\' will process all deps_os '
1281 'references')
1282 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1283 help='Skip svn up whenever possible by requesting '
1284 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001285 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001286 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001287
1288 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001289 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001290
maruel@chromium.org307d1792010-05-31 20:03:13 +00001291 if options.revisions and options.head:
1292 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001293 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001294
1295 if options.verbose:
1296 # Print out the .gclient file. This is longer than if we just printed the
1297 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001298 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001299 return client.RunOnDeps('update', args)
1300
1301
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001302def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001303 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001304 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001305
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001306def CMDdiff(parser, args):
1307 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001308 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1309 help='override deps for the specified (comma-separated) '
1310 'platform(s); \'all\' will process all deps_os '
1311 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001312 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001313 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001314 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001315 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001316 if options.verbose:
1317 # Print out the .gclient file. This is longer than if we just printed the
1318 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001319 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001320 return client.RunOnDeps('diff', args)
1321
1322
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001323def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001324 """Revert all modifications in every dependencies.
1325
1326 That's the nuclear option to get back to a 'clean' state. It removes anything
1327 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001328 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1329 help='override deps for the specified (comma-separated) '
1330 'platform(s); \'all\' will process all deps_os '
1331 'references')
1332 parser.add_option('-n', '--nohooks', action='store_true',
1333 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001334 (options, args) = parser.parse_args(args)
1335 # --force is implied.
1336 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001337 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001338 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001339 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001340 return client.RunOnDeps('revert', args)
1341
1342
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001343def CMDrunhooks(parser, args):
1344 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001345 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1346 help='override deps for the specified (comma-separated) '
1347 'platform(s); \'all\' will process all deps_os '
1348 'references')
1349 parser.add_option('-f', '--force', action='store_true', default=True,
1350 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001351 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001352 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001353 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001354 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001355 if options.verbose:
1356 # Print out the .gclient file. This is longer than if we just printed the
1357 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001358 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001359 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001360 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001361 return client.RunOnDeps('runhooks', args)
1362
1363
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001364def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001365 """Output revision info mapping for the client and its dependencies.
1366
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001367 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001368 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001369 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1370 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001371 commit can change.
1372 """
1373 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1374 help='override deps for the specified (comma-separated) '
1375 'platform(s); \'all\' will process all deps_os '
1376 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001377 parser.add_option('-a', '--actual', action='store_true',
1378 help='gets the actual checked out revisions instead of the '
1379 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001380 parser.add_option('-s', '--snapshot', action='store_true',
1381 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001382 'version of all repositories to reproduce the tree, '
1383 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001384 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001385 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001386 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001387 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001388 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001389 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001390
1391
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001392def Command(name):
1393 return getattr(sys.modules[__name__], 'CMD' + name, None)
1394
1395
1396def CMDhelp(parser, args):
1397 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001398 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001399 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001400 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001401 parser.print_help()
1402 return 0
1403
1404
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001405def GenUsage(parser, command):
1406 """Modify an OptParse object with the function's documentation."""
1407 obj = Command(command)
1408 if command == 'help':
1409 command = '<command>'
1410 # OptParser.description prefer nicely non-formatted strings.
1411 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1412 usage = getattr(obj, 'usage', '')
1413 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1414 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001415
1416
maruel@chromium.org0895b752011-08-26 20:40:33 +00001417def Parser():
1418 """Returns the default parser."""
1419 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org8fbb7762011-09-14 17:44:53 +00001420 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org0895b752011-08-26 20:40:33 +00001421 help='Specify how many SCM commands can run in parallel; '
1422 'default=%default')
1423 parser.add_option('-v', '--verbose', action='count', default=0,
1424 help='Produces additional output for diagnostics. Can be '
1425 'used up to three times for more logging info.')
1426 parser.add_option('--gclientfile', dest='config_filename',
1427 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1428 help='Specify an alternate %default file')
1429 # Integrate standard options processing.
1430 old_parser = parser.parse_args
1431 def Parse(args):
1432 (options, args) = old_parser(args)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001433 level = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG][
1434 min(options.verbose, 3)]
maruel@chromium.org0895b752011-08-26 20:40:33 +00001435 logging.basicConfig(level=level,
1436 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1437 options.entries_filename = options.config_filename + '_entries'
1438 if options.jobs < 1:
1439 parser.error('--jobs must be 1 or higher')
1440
1441 # These hacks need to die.
1442 if not hasattr(options, 'revisions'):
1443 # GClient.RunOnDeps expects it even if not applicable.
1444 options.revisions = []
1445 if not hasattr(options, 'head'):
1446 options.head = None
1447 if not hasattr(options, 'nohooks'):
1448 options.nohooks = True
1449 if not hasattr(options, 'deps_os'):
1450 options.deps_os = None
1451 if not hasattr(options, 'manually_grab_svn_rev'):
1452 options.manually_grab_svn_rev = None
1453 if not hasattr(options, 'force'):
1454 options.force = None
1455 return (options, args)
1456 parser.parse_args = Parse
1457 # We don't want wordwrapping in epilog (usually examples)
1458 parser.format_epilog = lambda _: parser.epilog or ''
1459 return parser
1460
1461
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001462def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001463 """Doesn't parse the arguments here, just find the right subcommand to
1464 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001465 if sys.hexversion < 0x02050000:
1466 print >> sys.stderr, (
1467 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001468 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001469 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1470 # operations. Python as a strong tendency to buffer sys.stdout.
1471 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001472 # Make stdout annotated with the thread ids.
1473 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001474 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001475 # Unused variable 'usage'
1476 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001477 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1478 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1479 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
maruel@chromium.org0895b752011-08-26 20:40:33 +00001480 parser = Parser()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001481 if argv:
1482 command = Command(argv[0])
1483 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001484 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001485 GenUsage(parser, argv[0])
1486 return command(parser, argv[1:])
1487 # Not a known command. Default to help.
1488 GenUsage(parser, 'help')
1489 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001490 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001491 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001492 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001493
1494
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001495if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001496 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001497 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001498
1499# vim: ts=2:sw=2:tw=80:et: