blob: 9a678220f117760ad2c9c5c4cdefa21705a35839 [file] [log] [blame]
[email protected]725f1c32011-04-01 20:24:541#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
[email protected]ba551772010-02-03 18:21:423# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
[email protected]fb2b8eb2009-04-23 21:03:425
[email protected]0b6a0842010-06-15 14:34:196"""Meta checkout manager supporting both Subversion and GIT.
[email protected]fb2b8eb2009-04-23 21:03:427
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
[email protected]67820ef2009-07-27 17:23:0024 working copy as a result of a "sync"/"update" or "revert" operation. This
[email protected]0b6a0842010-06-15 14:34:1925 can be prevented by using --nohooks (hooks run by default). Hooks can also
[email protected]5df6a462009-08-28 18:52:2626 be forced to run with the "runhooks" operation. If "sync" is run with
[email protected]fb2b8eb2009-04-23 21:03:4227 --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
[email protected]71b40682009-07-31 23:40:0941 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.
[email protected]fb2b8eb2009-04-23 21:03:4244
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
[email protected]46304292010-10-28 11:42:0052__version__ = "0.6.1"
[email protected]fb2b8eb2009-04-23 21:03:4253
[email protected]9e5317a2010-08-13 20:35:1154import copy
[email protected]754960e2009-09-21 12:31:0555import logging
[email protected]fb2b8eb2009-04-23 21:03:4256import optparse
57import os
[email protected]621939b2010-08-10 20:12:0058import posixpath
[email protected]2e38de72009-09-28 17:04:4759import pprint
[email protected]fb2b8eb2009-04-23 21:03:4260import re
[email protected]fb2b8eb2009-04-23 21:03:4261import sys
[email protected]fb2b8eb2009-04-23 21:03:4262import urlparse
[email protected]fb2b8eb2009-04-23 21:03:4263import urllib
64
[email protected]cb2985f2010-11-03 14:08:3165import breakpad # pylint: disable=W0611
[email protected]ada4c652009-12-03 15:32:0166
[email protected]35625c72011-03-23 17:34:0267import fix_encoding
[email protected]5f3eee32009-09-17 00:34:3068import gclient_scm
69import gclient_utils
[email protected]1f7a3d12010-02-04 15:11:5070from third_party.repo.progress import Progress
[email protected]31cb48a2011-04-04 18:01:3671import subprocess2
[email protected]fb2b8eb2009-04-23 21:03:4272
[email protected]fb2b8eb2009-04-23 21:03:4273
[email protected]cb2985f2010-11-03 14:08:3174def attr(attribute, data):
[email protected]1f7d1182010-05-17 18:17:3875 """Sets an attribute on a function."""
76 def hook(fn):
[email protected]cb2985f2010-11-03 14:08:3177 setattr(fn, attribute, data)
[email protected]1f7d1182010-05-17 18:17:3878 return fn
79 return hook
[email protected]e3da35f2010-03-09 21:40:4580
[email protected]fb2b8eb2009-04-23 21:03:4281
[email protected]fb2b8eb2009-04-23 21:03:4282## GClient implementation.
83
84
[email protected]116704f2010-06-11 17:34:3885class 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
[email protected]116704f2010-06-11 17:34:38102 class FileImpl(object):
103 """Used to implement the File('') syntax which lets you sync a single file
[email protected]e3216c62010-07-08 03:31:43104 from a SVN repo."""
[email protected]116704f2010-06-11 17:34:38105
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
[email protected]80cbe8b2010-08-13 13:53:07139class Dependency(GClientKeywords, gclient_utils.WorkItem):
[email protected]54a07a22010-06-14 19:07:39140 """Object that represents a dependency checkout."""
[email protected]9eda4112010-06-11 18:56:10141 DEPS_FILE = 'DEPS'
[email protected]0b6a0842010-06-15 14:34:19142
[email protected]0d812442010-08-10 12:41:08143 def __init__(self, parent, name, url, safesync_url, custom_deps,
[email protected]f50907b2010-08-12 17:05:48144 custom_vars, deps_file, should_process):
[email protected]54a07a22010-06-14 19:07:39145 GClientKeywords.__init__(self)
[email protected]6985efc2010-09-08 13:26:12146 gclient_utils.WorkItem.__init__(self)
[email protected]54a07a22010-06-14 19:07:39147 self.parent = parent
148 self.name = name
149 self.url = url
[email protected]df2b3152010-07-21 17:35:24150 self.parsed_url = None
[email protected]54a07a22010-06-14 19:07:39151 # These 2 are only set in .gclient and not in DEPS files.
152 self.safesync_url = safesync_url
153 self.custom_vars = custom_vars or {}
154 self.custom_deps = custom_deps or {}
[email protected]0b6a0842010-06-15 14:34:19155 self.deps_hooks = []
[email protected]54a07a22010-06-14 19:07:39156 self.dependencies = []
157 self.deps_file = deps_file or self.DEPS_FILE
[email protected]df2b3152010-07-21 17:35:24158 # A cache of the files affected by the current operation, necessary for
159 # hooks.
[email protected]861fd0f2010-07-23 03:05:05160 self._file_list = []
[email protected]85c2a192010-07-22 21:14:43161 # If it is not set to True, the dependency wasn't processed for its child
162 # dependency, i.e. its DEPS wasn't read.
[email protected]271375b2010-06-23 19:17:38163 self.deps_parsed = False
[email protected]f50907b2010-08-12 17:05:48164 # This dependency should be processed, i.e. checked out
165 self.should_process = should_process
[email protected]f3abb802010-08-10 17:19:56166 # This dependency has been processed, i.e. checked out
167 self.processed = False
168 # This dependency had its hook run
169 self.hooks_ran = False
[email protected]621939b2010-08-10 20:12:00170 # Required dependencies to run before running this one:
171 self.requirements = []
172 if self.parent and self.parent.name:
173 self.requirements.append(self.parent.name)
174 if isinstance(self.url, self.FromImpl):
175 self.requirements.append(self.url.module_name)
[email protected]fb2b8eb2009-04-23 21:03:42176
[email protected]54a07a22010-06-14 19:07:39177 # Sanity checks
178 if not self.name and self.parent:
179 raise gclient_utils.Error('Dependency without name')
180 if not isinstance(self.url,
181 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
182 raise gclient_utils.Error('dependency url must be either a string, None, '
183 'File() or From() instead of %s' %
184 self.url.__class__.__name__)
185 if '/' in self.deps_file or '\\' in self.deps_file:
186 raise gclient_utils.Error('deps_file name must not be a path, just a '
187 'filename. %s' % self.deps_file)
188
[email protected]df2b3152010-07-21 17:35:24189 def LateOverride(self, url):
[email protected]da7a1f92010-08-10 17:19:02190 """Resolves the parsed url from url.
191
192 Manages From() keyword accordingly. Do not touch self.parsed_url nor
193 self.url because it may called with other urls due to From()."""
[email protected]f50907b2010-08-12 17:05:48194 assert self.parsed_url == None or not self.should_process, self.parsed_url
[email protected]df2b3152010-07-21 17:35:24195 overriden_url = self.get_custom_deps(self.name, url)
196 if overriden_url != url:
[email protected]dde32ee2010-08-10 17:44:05197 logging.info('%s, %s was overriden to %s' % (self.name, url,
[email protected]da7a1f92010-08-10 17:19:02198 overriden_url))
199 return overriden_url
[email protected]df2b3152010-07-21 17:35:24200 elif isinstance(url, self.FromImpl):
201 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
[email protected]f3abb802010-08-10 17:19:56202 if not ref:
203 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
204 url.module_name, ref))
205 # It may happen that len(ref) > 1 but it's no big deal.
[email protected]df2b3152010-07-21 17:35:24206 ref = ref[0]
[email protected]98d05fa2010-07-22 21:58:01207 sub_target = url.sub_target_name or self.name
[email protected]df2b3152010-07-21 17:35:24208 # Make sure the referenced dependency DEPS file is loaded and file the
209 # inner referenced dependency.
[email protected]f50907b2010-08-12 17:05:48210 ref.ParseDepsFile()
[email protected]df2b3152010-07-21 17:35:24211 found_dep = None
212 for d in ref.dependencies:
213 if d.name == sub_target:
214 found_dep = d
215 break
216 if not found_dep:
[email protected]f3abb802010-08-10 17:19:56217 raise gclient_utils.Error(
[email protected]dde32ee2010-08-10 17:44:05218 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
219 sub_target, ref.name, self.name, str(self.root_parent())))
[email protected]df2b3152010-07-21 17:35:24220 # Call LateOverride() again.
[email protected]da7a1f92010-08-10 17:19:02221 parsed_url = found_dep.LateOverride(found_dep.url)
[email protected]dde32ee2010-08-10 17:44:05222 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
[email protected]da7a1f92010-08-10 17:19:02223 return parsed_url
[email protected]df2b3152010-07-21 17:35:24224 elif isinstance(url, basestring):
225 parsed_url = urlparse.urlparse(url)
226 if not parsed_url[0]:
227 # A relative url. Fetch the real base.
228 path = parsed_url[2]
229 if not path.startswith('/'):
230 raise gclient_utils.Error(
231 'relative DEPS entry \'%s\' must begin with a slash' % url)
232 # Create a scm just to query the full url.
233 parent_url = self.parent.parsed_url
234 if isinstance(parent_url, self.FileImpl):
235 parent_url = parent_url.file_location
236 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
[email protected]da7a1f92010-08-10 17:19:02237 parsed_url = scm.FullUrlForRelativeUrl(url)
[email protected]df2b3152010-07-21 17:35:24238 else:
[email protected]da7a1f92010-08-10 17:19:02239 parsed_url = url
[email protected]dde32ee2010-08-10 17:44:05240 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
[email protected]da7a1f92010-08-10 17:19:02241 return parsed_url
[email protected]df2b3152010-07-21 17:35:24242 elif isinstance(url, self.FileImpl):
[email protected]da7a1f92010-08-10 17:19:02243 parsed_url = url
[email protected]dde32ee2010-08-10 17:44:05244 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
[email protected]da7a1f92010-08-10 17:19:02245 return parsed_url
246 elif url is None:
247 return None
248 else:
249 raise gclient_utils.Error('Unkown url type')
[email protected]df2b3152010-07-21 17:35:24250
[email protected]f50907b2010-08-12 17:05:48251 def ParseDepsFile(self):
[email protected]271375b2010-06-23 19:17:38252 """Parses the DEPS file for this dependency."""
[email protected]f50907b2010-08-12 17:05:48253 assert self.processed == True
[email protected]df2b3152010-07-21 17:35:24254 if self.deps_parsed:
[email protected]dde32ee2010-08-10 17:44:05255 logging.debug('%s was already parsed' % self.name)
[email protected]df2b3152010-07-21 17:35:24256 return
[email protected]271375b2010-06-23 19:17:38257 self.deps_parsed = True
[email protected]271375b2010-06-23 19:17:38258 # One thing is unintuitive, vars= {} must happen before Var() use.
259 local_scope = {}
260 var = self.VarImpl(self.custom_vars, local_scope)
261 global_scope = {
262 'File': self.FileImpl,
263 'From': self.FromImpl,
264 'Var': var.Lookup,
265 'deps_os': {},
266 }
[email protected]46304292010-10-28 11:42:00267 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
268 if not os.path.isfile(filepath):
269 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
270 else:
271 deps_content = gclient_utils.FileRead(filepath)
272 logging.debug(deps_content)
273 # Eval the content.
274 try:
275 exec(deps_content, global_scope, local_scope)
276 except SyntaxError, e:
277 gclient_utils.SyntaxErrorToError(filepath, e)
[email protected]271375b2010-06-23 19:17:38278 deps = local_scope.get('deps', {})
[email protected]fb2b8eb2009-04-23 21:03:42279 # load os specific dependencies if defined. these dependencies may
280 # override or extend the values defined by the 'deps' member.
[email protected]271375b2010-06-23 19:17:38281 if 'deps_os' in local_scope:
282 for deps_os_key in self.enforced_os():
283 os_deps = local_scope['deps_os'].get(deps_os_key, {})
284 if len(self.enforced_os()) > 1:
285 # Ignore any conflict when including deps for more than one
[email protected]46304292010-10-28 11:42:00286 # platform, so we collect the broadest set of dependencies
287 # available. We may end up with the wrong revision of something for
288 # our platform, but this is the best we can do.
[email protected]fb2b8eb2009-04-23 21:03:42289 deps.update([x for x in os_deps.items() if not x[0] in deps])
290 else:
291 deps.update(os_deps)
292
[email protected]271375b2010-06-23 19:17:38293 self.deps_hooks.extend(local_scope.get('hooks', []))
294
295 # If a line is in custom_deps, but not in the solution, we want to append
296 # this line to the solution.
297 for d in self.custom_deps:
298 if d not in deps:
299 deps[d] = self.custom_deps[d]
[email protected]fb2b8eb2009-04-23 21:03:42300
301 # If use_relative_paths is set in the DEPS file, regenerate
302 # the dictionary using paths relative to the directory containing
303 # the DEPS file.
[email protected]271375b2010-06-23 19:17:38304 use_relative_paths = local_scope.get('use_relative_paths', False)
305 if use_relative_paths:
[email protected]fb2b8eb2009-04-23 21:03:42306 rel_deps = {}
307 for d, url in deps.items():
308 # normpath is required to allow DEPS to use .. in their
309 # dependency local path.
[email protected]271375b2010-06-23 19:17:38310 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
311 deps = rel_deps
[email protected]fb2b8eb2009-04-23 21:03:42312
[email protected]df2b3152010-07-21 17:35:24313 # Convert the deps into real Dependency.
314 for name, url in deps.iteritems():
315 if name in [s.name for s in self.dependencies]:
[email protected]dde32ee2010-08-10 17:44:05316 raise gclient_utils.Error(
317 'The same name "%s" appears multiple times in the deps section' %
318 name)
[email protected]f50907b2010-08-12 17:05:48319 should_process = self.recursion_limit() > 0 and self.should_process
320 if should_process:
321 tree = dict((d.name, d) for d in self.tree(False))
322 if name in tree:
323 if url == tree[name].url:
324 logging.info('Won\'t process duplicate dependency %s' % tree[name])
325 # In theory we could keep it as a shadow of the other one. In
326 # practice, simply ignore it.
327 #should_process = False
328 continue
329 else:
330 raise gclient_utils.Error(
331 'Dependency %s specified more than once:\n %s\nvs\n %s' %
332 (name, tree[name].hierarchy(), self.hierarchy()))
[email protected]0d812442010-08-10 12:41:08333 self.dependencies.append(Dependency(self, name, url, None, None, None,
[email protected]f50907b2010-08-12 17:05:48334 None, should_process))
[email protected]dde32ee2010-08-10 17:44:05335 logging.debug('Loaded: %s' % str(self))
[email protected]fb2b8eb2009-04-23 21:03:42336
[email protected]b17b55b2010-11-03 14:42:37337 # Arguments number differs from overridden method
338 # pylint: disable=W0221
[email protected]3742c842010-09-09 19:27:14339 def run(self, revision_overrides, command, args, work_queue, options):
[email protected]df2b3152010-07-21 17:35:24340 """Runs 'command' before parsing the DEPS in case it's a initial checkout
341 or a revert."""
[email protected]861fd0f2010-07-23 03:05:05342 assert self._file_list == []
[email protected]f50907b2010-08-12 17:05:48343 if not self.should_process:
344 return
[email protected]df2b3152010-07-21 17:35:24345 # When running runhooks, there's no need to consult the SCM.
346 # All known hooks are expected to run unconditionally regardless of working
347 # copy state, so skip the SCM status check.
348 run_scm = command not in ('runhooks', None)
[email protected]da7a1f92010-08-10 17:19:02349 self.parsed_url = self.LateOverride(self.url)
[email protected]df2b3152010-07-21 17:35:24350 if run_scm and self.parsed_url:
351 if isinstance(self.parsed_url, self.FileImpl):
352 # Special support for single-file checkout.
353 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
354 options.revision = self.parsed_url.GetRevision()
355 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
356 self.root_dir(),
357 self.name)
358 scm.RunCommand('updatesingle', options,
359 args + [self.parsed_url.GetFilename()],
[email protected]861fd0f2010-07-23 03:05:05360 self._file_list)
[email protected]df2b3152010-07-21 17:35:24361 else:
[email protected]9e5317a2010-08-13 20:35:11362 # Create a shallow copy to mutate revision.
363 options = copy.copy(options)
[email protected]df2b3152010-07-21 17:35:24364 options.revision = revision_overrides.get(self.name)
365 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
[email protected]861fd0f2010-07-23 03:05:05366 scm.RunCommand(command, options, args, self._file_list)
367 self._file_list = [os.path.join(self.name, f.strip())
368 for f in self._file_list]
[email protected]f3abb802010-08-10 17:19:56369 self.processed = True
[email protected]f50907b2010-08-12 17:05:48370 if self.recursion_limit() > 0:
[email protected]df2b3152010-07-21 17:35:24371 # Then we can parse the DEPS file.
[email protected]f50907b2010-08-12 17:05:48372 self.ParseDepsFile()
[email protected]621939b2010-08-10 20:12:00373 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
374 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
[email protected]049bced2010-08-12 13:37:20375 # src/foo. Yes, it's O(n^2)... It's important to do that before
376 # enqueueing them.
[email protected]621939b2010-08-10 20:12:00377 for s in self.dependencies:
378 for s2 in self.dependencies:
379 if s is s2:
380 continue
381 if s.name.startswith(posixpath.join(s2.name, '')):
382 s.requirements.append(s2.name)
383
[email protected]df2b3152010-07-21 17:35:24384 # Parse the dependencies of this dependency.
385 for s in self.dependencies:
[email protected]049bced2010-08-12 13:37:20386 work_queue.enqueue(s)
[email protected]fb2b8eb2009-04-23 21:03:42387
[email protected]df2b3152010-07-21 17:35:24388 def RunHooksRecursively(self, options):
[email protected]049bced2010-08-12 13:37:20389 """Evaluates all hooks, running actions as needed. run()
[email protected]df2b3152010-07-21 17:35:24390 must have been called before to load the DEPS."""
[email protected]f50907b2010-08-12 17:05:48391 assert self.hooks_ran == False
392 if not self.should_process or self.recursion_limit() <= 0:
393 # Don't run the hook when it is above recursion_limit.
394 return
[email protected]dc7445d2010-07-09 21:05:29395 # If "--force" was specified, run all hooks regardless of what files have
[email protected]df2b3152010-07-21 17:35:24396 # changed.
[email protected]f50907b2010-08-12 17:05:48397 if self.deps_hooks:
[email protected]df2b3152010-07-21 17:35:24398 # TODO(maruel): If the user is using git or git-svn, then we don't know
399 # what files have changed so we always run all hooks. It'd be nice to fix
400 # that.
401 if (options.force or
402 isinstance(self.parsed_url, self.FileImpl) or
403 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
404 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
405 for hook_dict in self.deps_hooks:
406 self._RunHookAction(hook_dict, [])
407 else:
408 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
409 # Convert all absolute paths to relative.
[email protected]861fd0f2010-07-23 03:05:05410 file_list = self.file_list()
411 for i in range(len(file_list)):
[email protected]df2b3152010-07-21 17:35:24412 # It depends on the command being executed (like runhooks vs sync).
[email protected]861fd0f2010-07-23 03:05:05413 if not os.path.isabs(file_list[i]):
[email protected]df2b3152010-07-21 17:35:24414 continue
[email protected]dc7445d2010-07-09 21:05:29415
[email protected]df2b3152010-07-21 17:35:24416 prefix = os.path.commonprefix([self.root_dir().lower(),
[email protected]861fd0f2010-07-23 03:05:05417 file_list[i].lower()])
418 file_list[i] = file_list[i][len(prefix):]
[email protected]df2b3152010-07-21 17:35:24419
420 # Strip any leading path separators.
[email protected]861fd0f2010-07-23 03:05:05421 while (file_list[i].startswith('\\') or
422 file_list[i].startswith('/')):
423 file_list[i] = file_list[i][1:]
[email protected]df2b3152010-07-21 17:35:24424
425 # Run hooks on the basis of whether the files from the gclient operation
426 # match each hook's pattern.
427 for hook_dict in self.deps_hooks:
428 pattern = re.compile(hook_dict['pattern'])
[email protected]861fd0f2010-07-23 03:05:05429 matching_file_list = [f for f in file_list if pattern.search(f)]
[email protected]df2b3152010-07-21 17:35:24430 if matching_file_list:
431 self._RunHookAction(hook_dict, matching_file_list)
[email protected]f50907b2010-08-12 17:05:48432 for s in self.dependencies:
433 s.RunHooksRecursively(options)
[email protected]fb2b8eb2009-04-23 21:03:42434
[email protected]eaf61062010-07-07 18:42:39435 def _RunHookAction(self, hook_dict, matching_file_list):
436 """Runs the action from a single hook."""
[email protected]dde32ee2010-08-10 17:44:05437 # A single DEPS file can specify multiple hooks so this function can be
438 # called multiple times on a single Dependency.
439 #assert self.hooks_ran == False
[email protected]f3abb802010-08-10 17:19:56440 self.hooks_ran = True
[email protected]dde32ee2010-08-10 17:44:05441 logging.debug(hook_dict)
442 logging.debug(matching_file_list)
[email protected]eaf61062010-07-07 18:42:39443 command = hook_dict['action'][:]
444 if command[0] == 'python':
445 # If the hook specified "python" as the first item, the action is a
446 # Python script. Run it by starting a new copy of the same
447 # interpreter.
448 command[0] = sys.executable
449
450 if '$matching_files' in command:
451 splice_index = command.index('$matching_files')
452 command[splice_index:splice_index + 1] = matching_file_list
453
[email protected]17d01792010-09-01 18:07:10454 try:
455 gclient_utils.CheckCallAndFilterAndHeader(
456 command, cwd=self.root_dir(), always=True)
[email protected]31cb48a2011-04-04 18:01:36457 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
[email protected]17d01792010-09-01 18:07:10458 # Use a discrete exit status code of 2 to indicate that a hook action
459 # failed. Users of this script may wish to treat hook action failures
460 # differently from VC failures.
461 print >> sys.stderr, 'Error: %s' % str(e)
462 sys.exit(2)
[email protected]eaf61062010-07-07 18:42:39463
[email protected]271375b2010-06-23 19:17:38464 def root_dir(self):
465 return self.parent.root_dir()
466
467 def enforced_os(self):
468 return self.parent.enforced_os()
469
[email protected]d36fba82010-06-28 16:50:40470 def recursion_limit(self):
471 return self.parent.recursion_limit() - 1
472
[email protected]0d812442010-08-10 12:41:08473 def tree(self, include_all):
474 return self.parent.tree(include_all)
[email protected]d36fba82010-06-28 16:50:40475
[email protected]0d812442010-08-10 12:41:08476 def subtree(self, include_all):
[email protected]f50907b2010-08-12 17:05:48477 """Breadth first"""
[email protected]c57e4f22010-07-22 21:37:46478 result = []
[email protected]f50907b2010-08-12 17:05:48479 for d in self.dependencies:
480 if d.should_process or include_all:
[email protected]044f4e32010-07-22 21:59:57481 result.append(d)
[email protected]f50907b2010-08-12 17:05:48482 for d in self.dependencies:
483 result.extend(d.subtree(include_all))
[email protected]c57e4f22010-07-22 21:37:46484 return result
485
[email protected]d36fba82010-06-28 16:50:40486 def get_custom_deps(self, name, url):
487 """Returns a custom deps if applicable."""
488 if self.parent:
489 url = self.parent.get_custom_deps(name, url)
490 # None is a valid return value to disable a dependency.
491 return self.custom_deps.get(name, url)
492
[email protected]861fd0f2010-07-23 03:05:05493 def file_list(self):
494 result = self._file_list[:]
495 for d in self.dependencies:
496 result.extend(d.file_list())
497 return result
498
[email protected]d36fba82010-06-28 16:50:40499 def __str__(self):
500 out = []
[email protected]dde32ee2010-08-10 17:44:05501 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
[email protected]f50907b2010-08-12 17:05:48502 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
503 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
[email protected]d36fba82010-06-28 16:50:40504 # 'deps_file'
505 if self.__dict__[i]:
506 out.append('%s: %s' % (i, self.__dict__[i]))
507
508 for d in self.dependencies:
509 out.extend([' ' + x for x in str(d).splitlines()])
510 out.append('')
511 return '\n'.join(out)
512
513 def __repr__(self):
514 return '%s: %s' % (self.name, self.url)
515
[email protected]bffb9042010-07-22 20:59:36516 def hierarchy(self):
[email protected]bc2d2f92010-07-22 21:26:48517 """Returns a human-readable hierarchical reference to a Dependency."""
[email protected]bffb9042010-07-22 20:59:36518 out = '%s(%s)' % (self.name, self.url)
519 i = self.parent
520 while i and i.name:
521 out = '%s(%s) -> %s' % (i.name, i.url, out)
522 i = i.parent
523 return out
524
[email protected]dde32ee2010-08-10 17:44:05525 def root_parent(self):
526 """Returns the root object, normally a GClient object."""
527 d = self
528 while d.parent:
529 d = d.parent
530 return d
531
[email protected]9a66ddf2010-06-16 16:54:16532
533class GClient(Dependency):
[email protected]d36fba82010-06-28 16:50:40534 """Object that represent a gclient checkout. A tree of Dependency(), one per
535 solution or DEPS entry."""
[email protected]9a66ddf2010-06-16 16:54:16536
537 DEPS_OS_CHOICES = {
538 "win32": "win",
539 "win": "win",
540 "cygwin": "win",
541 "darwin": "mac",
542 "mac": "mac",
543 "unix": "unix",
544 "linux": "unix",
545 "linux2": "unix",
546 }
547
548 DEFAULT_CLIENT_FILE_TEXT = ("""\
549solutions = [
550 { "name" : "%(solution_name)s",
551 "url" : "%(solution_url)s",
552 "custom_deps" : {
553 },
[email protected]73e21142010-07-05 13:32:01554 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16555 },
556]
557""")
558
559 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
560 { "name" : "%(solution_name)s",
561 "url" : "%(solution_url)s",
562 "custom_deps" : {
[email protected]73e21142010-07-05 13:32:01563%(solution_deps)s },
564 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16565 },
566""")
567
568 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
569# Snapshot generated with gclient revinfo --snapshot
570solutions = [
[email protected]73e21142010-07-05 13:32:01571%(solution_list)s]
[email protected]9a66ddf2010-06-16 16:54:16572""")
573
574 def __init__(self, root_dir, options):
[email protected]0d812442010-08-10 12:41:08575 # Do not change previous behavior. Only solution level and immediate DEPS
576 # are processed.
577 self._recursion_limit = 2
[email protected]f50907b2010-08-12 17:05:48578 Dependency.__init__(self, None, None, None, None, None, None, None, True)
[email protected]0d425922010-06-21 19:22:24579 self._options = options
[email protected]271375b2010-06-23 19:17:38580 if options.deps_os:
581 enforced_os = options.deps_os.split(',')
582 else:
583 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
584 if 'all' in enforced_os:
585 enforced_os = self.DEPS_OS_CHOICES.itervalues()
586 self._enforced_os = list(set(enforced_os))
587 self._root_dir = root_dir
[email protected]9a66ddf2010-06-16 16:54:16588 self.config_content = None
589
590 def SetConfig(self, content):
591 assert self.dependencies == []
592 config_dict = {}
593 self.config_content = content
594 try:
595 exec(content, config_dict)
596 except SyntaxError, e:
[email protected]5990f9d2010-07-07 18:02:58597 gclient_utils.SyntaxErrorToError('.gclient', e)
[email protected]9a66ddf2010-06-16 16:54:16598 for s in config_dict.get('solutions', []):
[email protected]81843b82010-06-28 16:49:26599 try:
[email protected]f50907b2010-08-12 17:05:48600 tree = dict((d.name, d) for d in self.tree(False))
601 if s['name'] in tree:
602 raise gclient_utils.Error(
603 'Dependency %s specified more than once in .gclient' % s['name'])
[email protected]81843b82010-06-28 16:49:26604 self.dependencies.append(Dependency(
605 self, s['name'], s['url'],
606 s.get('safesync_url', None),
607 s.get('custom_deps', {}),
[email protected]0d812442010-08-10 12:41:08608 s.get('custom_vars', {}),
[email protected]f50907b2010-08-12 17:05:48609 None,
610 True))
[email protected]81843b82010-06-28 16:49:26611 except KeyError:
612 raise gclient_utils.Error('Invalid .gclient file. Solution is '
613 'incomplete: %s' % s)
[email protected]9a66ddf2010-06-16 16:54:16614 # .gclient can have hooks.
615 self.deps_hooks = config_dict.get('hooks', [])
[email protected]049bced2010-08-12 13:37:20616 self.deps_parsed = True
[email protected]9a66ddf2010-06-16 16:54:16617
618 def SaveConfig(self):
619 gclient_utils.FileWrite(os.path.join(self.root_dir(),
620 self._options.config_filename),
621 self.config_content)
622
623 @staticmethod
624 def LoadCurrentConfig(options):
625 """Searches for and loads a .gclient file relative to the current working
626 dir. Returns a GClient object."""
[email protected]15804092010-09-02 17:07:37627 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
[email protected]9a66ddf2010-06-16 16:54:16628 if not path:
629 return None
630 client = GClient(path, options)
631 client.SetConfig(gclient_utils.FileRead(
632 os.path.join(path, options.config_filename)))
[email protected]15804092010-09-02 17:07:37633 return client
[email protected]9a66ddf2010-06-16 16:54:16634
635 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
636 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
637 'solution_name': solution_name,
638 'solution_url': solution_url,
639 'safesync_url' : safesync_url,
640 })
641
[email protected]df2b3152010-07-21 17:35:24642 def _SaveEntries(self):
[email protected]9a66ddf2010-06-16 16:54:16643 """Creates a .gclient_entries file to record the list of unique checkouts.
644
645 The .gclient_entries file lives in the same directory as .gclient.
[email protected]9a66ddf2010-06-16 16:54:16646 """
647 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
648 # makes testing a bit too fun.
[email protected]df2b3152010-07-21 17:35:24649 result = 'entries = {\n'
650 for entry in self.tree(False):
651 # Skip over File() dependencies as we can't version them.
652 if not isinstance(entry.parsed_url, self.FileImpl):
653 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
654 pprint.pformat(entry.parsed_url))
655 result += '}\n'
[email protected]9a66ddf2010-06-16 16:54:16656 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
[email protected]df2b3152010-07-21 17:35:24657 logging.info(result)
658 gclient_utils.FileWrite(file_path, result)
[email protected]9a66ddf2010-06-16 16:54:16659
660 def _ReadEntries(self):
661 """Read the .gclient_entries file for the given client.
662
663 Returns:
664 A sequence of solution names, which will be empty if there is the
665 entries file hasn't been created yet.
666 """
667 scope = {}
668 filename = os.path.join(self.root_dir(), self._options.entries_filename)
669 if not os.path.exists(filename):
[email protected]73e21142010-07-05 13:32:01670 return {}
[email protected]5990f9d2010-07-07 18:02:58671 try:
672 exec(gclient_utils.FileRead(filename), scope)
673 except SyntaxError, e:
674 gclient_utils.SyntaxErrorToError(filename, e)
[email protected]9a66ddf2010-06-16 16:54:16675 return scope['entries']
676
[email protected]54a07a22010-06-14 19:07:39677 def _EnforceRevisions(self):
[email protected]918a9ae2010-05-28 15:50:30678 """Checks for revision overrides."""
679 revision_overrides = {}
[email protected]307d1792010-05-31 20:03:13680 if self._options.head:
681 return revision_overrides
[email protected]792ea882010-11-10 02:37:27682 # Do not check safesync_url if one or more --revision flag is specified.
683 if not self._options.revisions:
684 for s in self.dependencies:
685 if not s.safesync_url:
686 continue
687 handle = urllib.urlopen(s.safesync_url)
688 rev = handle.read().strip()
689 handle.close()
690 if len(rev):
691 self._options.revisions.append('%s@%s' % (s.name, rev))
[email protected]307d1792010-05-31 20:03:13692 if not self._options.revisions:
693 return revision_overrides
[email protected]54a07a22010-06-14 19:07:39694 solutions_names = [s.name for s in self.dependencies]
[email protected]307d1792010-05-31 20:03:13695 index = 0
696 for revision in self._options.revisions:
697 if not '@' in revision:
698 # Support for --revision 123
699 revision = '%s@%s' % (solutions_names[index], revision)
[email protected]54a07a22010-06-14 19:07:39700 sol, rev = revision.split('@', 1)
[email protected]307d1792010-05-31 20:03:13701 if not sol in solutions_names:
702 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
703 print >> sys.stderr, ('Please fix your script, having invalid '
704 '--revision flags will soon considered an error.')
705 else:
[email protected]918a9ae2010-05-28 15:50:30706 revision_overrides[sol] = rev
[email protected]307d1792010-05-31 20:03:13707 index += 1
[email protected]918a9ae2010-05-28 15:50:30708 return revision_overrides
709
[email protected]fb2b8eb2009-04-23 21:03:42710 def RunOnDeps(self, command, args):
711 """Runs a command on each dependency in a client and its dependencies.
712
[email protected]fb2b8eb2009-04-23 21:03:42713 Args:
714 command: The command to use (e.g., 'status' or 'diff')
715 args: list of str - extra arguments to add to the command line.
[email protected]fb2b8eb2009-04-23 21:03:42716 """
[email protected]54a07a22010-06-14 19:07:39717 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01718 raise gclient_utils.Error('No solution specified')
[email protected]54a07a22010-06-14 19:07:39719 revision_overrides = self._EnforceRevisions()
[email protected]df2b3152010-07-21 17:35:24720 pm = None
[email protected]5b3f8852010-09-10 16:49:54721 # Disable progress for non-tty stdout.
[email protected]a116e7d2010-10-05 19:58:02722 if (command in ('update', 'revert') and sys.stdout.isatty() and not
723 self._options.verbose):
[email protected]049bced2010-08-12 13:37:20724 pm = Progress('Syncing projects', 1)
[email protected]9e5317a2010-08-13 20:35:11725 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
[email protected]049bced2010-08-12 13:37:20726 for s in self.dependencies:
727 work_queue.enqueue(s)
[email protected]3742c842010-09-09 19:27:14728 work_queue.flush(revision_overrides, command, args, options=self._options)
[email protected]6f363722010-04-27 00:41:09729
[email protected]df2b3152010-07-21 17:35:24730 # Once all the dependencies have been processed, it's now safe to run the
731 # hooks.
732 if not self._options.nohooks:
733 self.RunHooksRecursively(self._options)
[email protected]fb2b8eb2009-04-23 21:03:42734
735 if command == 'update':
[email protected]cdcee802009-06-23 15:30:42736 # Notify the user if there is an orphaned entry in their working copy.
737 # Only delete the directory if there are no changes in it, and
738 # delete_unversioned_trees is set to true.
[email protected]9ea49d22011-03-08 15:30:47739 entries = [i.name for i in self.tree(False) if i.url]
[email protected]df2b3152010-07-21 17:35:24740 for entry, prev_url in self._ReadEntries().iteritems():
[email protected]04dd7de2010-10-14 13:25:49741 if not prev_url:
742 # entry must have been overridden via .gclient custom_deps
743 continue
[email protected]c5e9aec2009-08-03 18:25:56744 # Fix path separator on Windows.
745 entry_fixed = entry.replace('/', os.path.sep)
[email protected]75a59272010-06-11 22:34:03746 e_dir = os.path.join(self.root_dir(), entry_fixed)
[email protected]c5e9aec2009-08-03 18:25:56747 # Use entry and not entry_fixed there.
[email protected]0329e672009-05-13 18:41:04748 if entry not in entries and os.path.exists(e_dir):
[email protected]df2b3152010-07-21 17:35:24749 file_list = []
750 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
751 scm.status(self._options, [], file_list)
752 modified_files = file_list != []
[email protected]28d14bd2010-11-11 20:37:09753 if (not self._options.delete_unversioned_trees or
754 (modified_files and not self._options.force)):
[email protected]c5e9aec2009-08-03 18:25:56755 # There are modified files in this entry. Keep warning until
756 # removed.
[email protected]d36fba82010-06-28 16:50:40757 print(('\nWARNING: \'%s\' is no longer part of this client. '
758 'It is recommended that you manually remove it.\n') %
[email protected]c5e9aec2009-08-03 18:25:56759 entry_fixed)
[email protected]fb2b8eb2009-04-23 21:03:42760 else:
761 # Delete the entry
[email protected]73e21142010-07-05 13:32:01762 print('\n________ deleting \'%s\' in \'%s\'' % (
763 entry_fixed, self.root_dir()))
[email protected]5f3eee32009-09-17 00:34:30764 gclient_utils.RemoveDirectory(e_dir)
[email protected]fb2b8eb2009-04-23 21:03:42765 # record the current list of entries for next time
[email protected]df2b3152010-07-21 17:35:24766 self._SaveEntries()
[email protected]17cdf762010-05-28 17:30:52767 return 0
[email protected]fb2b8eb2009-04-23 21:03:42768
769 def PrintRevInfo(self):
[email protected]54a07a22010-06-14 19:07:39770 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01771 raise gclient_utils.Error('No solution specified')
[email protected]df2b3152010-07-21 17:35:24772 # Load all the settings.
[email protected]9e5317a2010-08-13 20:35:11773 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
[email protected]049bced2010-08-12 13:37:20774 for s in self.dependencies:
775 work_queue.enqueue(s)
[email protected]3742c842010-09-09 19:27:14776 work_queue.flush({}, None, [], options=self._options)
[email protected]fb2b8eb2009-04-23 21:03:42777
[email protected]6da25d02010-08-11 17:32:55778 def GetURLAndRev(dep):
779 """Returns the revision-qualified SCM url for a Dependency."""
780 if dep.parsed_url is None:
[email protected]baa578e2010-07-12 17:36:59781 return None
[email protected]6da25d02010-08-11 17:32:55782 if isinstance(dep.parsed_url, self.FileImpl):
783 original_url = dep.parsed_url.file_location
784 else:
785 original_url = dep.parsed_url
[email protected]5d63eb82010-03-24 23:22:09786 url, _ = gclient_utils.SplitUrlRevision(original_url)
[email protected]6da25d02010-08-11 17:32:55787 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
[email protected]df2b3152010-07-21 17:35:24788 if not os.path.isdir(scm.checkout_path):
789 return None
[email protected]baa578e2010-07-12 17:36:59790 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
[email protected]fb2b8eb2009-04-23 21:03:42791
[email protected]baa578e2010-07-12 17:36:59792 if self._options.snapshot:
[email protected]df2b3152010-07-21 17:35:24793 new_gclient = ''
794 # First level at .gclient
795 for d in self.dependencies:
796 entries = {}
[email protected]6da25d02010-08-11 17:32:55797 def GrabDeps(dep):
[email protected]df2b3152010-07-21 17:35:24798 """Recursively grab dependencies."""
[email protected]6da25d02010-08-11 17:32:55799 for d in dep.dependencies:
800 entries[d.name] = GetURLAndRev(d)
801 GrabDeps(d)
[email protected]df2b3152010-07-21 17:35:24802 GrabDeps(d)
803 custom_deps = []
804 for k in sorted(entries.keys()):
805 if entries[k]:
806 # Quotes aren't escaped...
807 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
808 else:
809 custom_deps.append(' \"%s\": None,\n' % k)
810 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
811 'solution_name': d.name,
812 'solution_url': d.url,
813 'safesync_url' : d.safesync_url or '',
814 'solution_deps': ''.join(custom_deps),
815 }
816 # Print the snapshot configuration file
817 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
[email protected]de8f3522010-03-11 23:47:44818 else:
[email protected]b1e315f2010-08-11 18:44:50819 entries = {}
820 for d in self.tree(False):
821 if self._options.actual:
822 entries[d.name] = GetURLAndRev(d)
823 else:
824 entries[d.name] = d.parsed_url
825 keys = sorted(entries.keys())
826 for x in keys:
[email protected]ce464892010-08-12 17:12:18827 print('%s: %s' % (x, entries[x]))
[email protected]dde32ee2010-08-10 17:44:05828 logging.info(str(self))
[email protected]fb2b8eb2009-04-23 21:03:42829
[email protected]f50907b2010-08-12 17:05:48830 def ParseDepsFile(self):
[email protected]d36fba82010-06-28 16:50:40831 """No DEPS to parse for a .gclient file."""
[email protected]049bced2010-08-12 13:37:20832 raise gclient_utils.Error('Internal error')
[email protected]d36fba82010-06-28 16:50:40833
[email protected]75a59272010-06-11 22:34:03834 def root_dir(self):
[email protected]d36fba82010-06-28 16:50:40835 """Root directory of gclient checkout."""
[email protected]75a59272010-06-11 22:34:03836 return self._root_dir
837
[email protected]271375b2010-06-23 19:17:38838 def enforced_os(self):
[email protected]d36fba82010-06-28 16:50:40839 """What deps_os entries that are to be parsed."""
[email protected]271375b2010-06-23 19:17:38840 return self._enforced_os
841
[email protected]d36fba82010-06-28 16:50:40842 def recursion_limit(self):
843 """How recursive can each dependencies in DEPS file can load DEPS file."""
844 return self._recursion_limit
845
[email protected]0d812442010-08-10 12:41:08846 def tree(self, include_all):
[email protected]d36fba82010-06-28 16:50:40847 """Returns a flat list of all the dependencies."""
[email protected]0d812442010-08-10 12:41:08848 return self.subtree(include_all)
[email protected]d36fba82010-06-28 16:50:40849
[email protected]fb2b8eb2009-04-23 21:03:42850
[email protected]5ca27692010-05-26 19:32:41851#### gclient commands.
[email protected]fb2b8eb2009-04-23 21:03:42852
853
[email protected]5ca27692010-05-26 19:32:41854def CMDcleanup(parser, args):
855 """Cleans up all working copies.
[email protected]ddff62d2010-05-17 21:02:36856
[email protected]5ca27692010-05-26 19:32:41857Mostly svn-specific. Simply runs 'svn cleanup' for each module.
[email protected]79692d62010-05-14 18:57:13858"""
[email protected]0b6a0842010-06-15 14:34:19859 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
860 help='override deps for the specified (comma-separated) '
861 'platform(s); \'all\' will process all deps_os '
862 'references')
[email protected]5ca27692010-05-26 19:32:41863 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34864 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42865 if not client:
[email protected]0b6a0842010-06-15 14:34:19866 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42867 if options.verbose:
868 # Print out the .gclient file. This is longer than if we just printed the
869 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38870 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42871 return client.RunOnDeps('cleanup', args)
872
873
[email protected]4b90e3a2010-07-01 20:28:26874@attr('usage', '[command] [args ...]')
875def CMDrecurse(parser, args):
876 """Operates on all the entries.
877
878 Runs a shell command on all entries.
879 """
880 # Stop parsing at the first non-arg so that these go through to the command
881 parser.disable_interspersed_args()
882 parser.add_option('-s', '--scm', action='append', default=[],
883 help='choose scm types to operate upon')
884 options, args = parser.parse_args(args)
[email protected]45e9f2d2010-10-18 13:33:46885 if not args:
886 print >> sys.stderr, 'Need to supply a command!'
887 return 1
[email protected]78cba522010-10-18 13:32:05888 root_and_entries = gclient_utils.GetGClientRootAndEntries()
889 if not root_and_entries:
890 print >> sys.stderr, (
891 'You need to run gclient sync at least once to use \'recurse\'.\n'
892 'This is because .gclient_entries needs to exist and be up to date.')
893 return 1
894 root, entries = root_and_entries
[email protected]4b90e3a2010-07-01 20:28:26895 scm_set = set()
896 for scm in options.scm:
897 scm_set.update(scm.split(','))
898
899 # Pass in the SCM type as an env variable
900 env = os.environ.copy()
901
902 for path, url in entries.iteritems():
903 scm = gclient_scm.GetScmName(url)
904 if scm_set and scm not in scm_set:
905 continue
[email protected]2b9aa8e2010-08-25 20:01:42906 cwd = os.path.normpath(os.path.join(root, path))
[email protected]ac610232010-10-13 14:01:31907 if scm:
908 env['GCLIENT_SCM'] = scm
909 if url:
910 env['GCLIENT_URL'] = url
911 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
912 return 0
[email protected]4b90e3a2010-07-01 20:28:26913
914
[email protected]5ca27692010-05-26 19:32:41915@attr('usage', '[url] [safesync url]')
916def CMDconfig(parser, args):
[email protected]ddff62d2010-05-17 21:02:36917 """Create a .gclient file in the current directory.
918
[email protected]5ca27692010-05-26 19:32:41919This specifies the configuration for further commands. After update/sync,
[email protected]79692d62010-05-14 18:57:13920top-level DEPS files in each module are read to determine dependent
[email protected]5ca27692010-05-26 19:32:41921modules to operate on as well. If optional [url] parameter is
[email protected]79692d62010-05-14 18:57:13922provided, then configuration is read from a specified Subversion server
[email protected]5ca27692010-05-26 19:32:41923URL.
[email protected]79692d62010-05-14 18:57:13924"""
[email protected]0b6a0842010-06-15 14:34:19925 parser.add_option('--spec',
926 help='create a gclient file containing the provided '
927 'string. Due to Cygwin/Python brokenness, it '
928 'probably can\'t contain any newlines.')
929 parser.add_option('--name',
930 help='overrides the default name for the solution')
[email protected]5ca27692010-05-26 19:32:41931 (options, args) = parser.parse_args(args)
[email protected]5fc2a332010-05-26 19:37:15932 if ((options.spec and args) or len(args) > 2 or
933 (not options.spec and not args)):
934 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
935
[email protected]0329e672009-05-13 18:41:04936 if os.path.exists(options.config_filename):
[email protected]0b6a0842010-06-15 14:34:19937 raise gclient_utils.Error('%s file already exists in the current directory'
[email protected]e3608df2009-11-10 20:22:57938 % options.config_filename)
[email protected]2806acc2009-05-15 12:33:34939 client = GClient('.', options)
[email protected]fb2b8eb2009-04-23 21:03:42940 if options.spec:
941 client.SetConfig(options.spec)
942 else:
[email protected]1ab7ffc2009-06-03 17:21:37943 base_url = args[0].rstrip('/')
[email protected]8cf7a392010-04-07 17:20:26944 if not options.name:
[email protected]0b6a0842010-06-15 14:34:19945 name = base_url.split('/')[-1]
[email protected]8cf7a392010-04-07 17:20:26946 else:
947 # specify an alternate relpath for the given URL.
948 name = options.name
[email protected]0b6a0842010-06-15 14:34:19949 safesync_url = ''
[email protected]fb2b8eb2009-04-23 21:03:42950 if len(args) > 1:
951 safesync_url = args[1]
952 client.SetDefaultConfig(name, base_url, safesync_url)
953 client.SaveConfig()
[email protected]79692d62010-05-14 18:57:13954 return 0
[email protected]fb2b8eb2009-04-23 21:03:42955
956
[email protected]5ca27692010-05-26 19:32:41957def CMDexport(parser, args):
[email protected]ddff62d2010-05-17 21:02:36958 """Wrapper for svn export for all managed directories."""
[email protected]0b6a0842010-06-15 14:34:19959 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
960 help='override deps for the specified (comma-separated) '
961 'platform(s); \'all\' will process all deps_os '
962 'references')
[email protected]5ca27692010-05-26 19:32:41963 (options, args) = parser.parse_args(args)
[email protected]644aa0c2009-07-17 20:20:41964 if len(args) != 1:
[email protected]0b6a0842010-06-15 14:34:19965 raise gclient_utils.Error('Need directory name')
[email protected]644aa0c2009-07-17 20:20:41966 client = GClient.LoadCurrentConfig(options)
967
968 if not client:
[email protected]0b6a0842010-06-15 14:34:19969 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]644aa0c2009-07-17 20:20:41970
971 if options.verbose:
972 # Print out the .gclient file. This is longer than if we just printed the
973 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38974 print(client.config_content)
[email protected]644aa0c2009-07-17 20:20:41975 return client.RunOnDeps('export', args)
976
[email protected]fb2b8eb2009-04-23 21:03:42977
[email protected]5ca27692010-05-26 19:32:41978@attr('epilog', """Example:
979 gclient pack > patch.txt
980 generate simple patch for configured client and dependences
981""")
982def CMDpack(parser, args):
[email protected]79692d62010-05-14 18:57:13983 """Generate a patch which can be applied at the root of the tree.
[email protected]ddff62d2010-05-17 21:02:36984
[email protected]5ca27692010-05-26 19:32:41985Internally, runs 'svn diff'/'git diff' on each checked out module and
[email protected]79692d62010-05-14 18:57:13986dependencies, and performs minimal postprocessing of the output. The
987resulting patch is printed to stdout and can be applied to a freshly
[email protected]5ca27692010-05-26 19:32:41988checked out tree via 'patch -p0 < patchfile'.
[email protected]79692d62010-05-14 18:57:13989"""
[email protected]0b6a0842010-06-15 14:34:19990 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
991 help='override deps for the specified (comma-separated) '
992 'platform(s); \'all\' will process all deps_os '
993 'references')
[email protected]5ca27692010-05-26 19:32:41994 (options, args) = parser.parse_args(args)
[email protected]ab318592009-09-04 00:54:55995 client = GClient.LoadCurrentConfig(options)
996 if not client:
[email protected]0b6a0842010-06-15 14:34:19997 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]ab318592009-09-04 00:54:55998 if options.verbose:
999 # Print out the .gclient file. This is longer than if we just printed the
1000 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381001 print(client.config_content)
[email protected]ab318592009-09-04 00:54:551002 return client.RunOnDeps('pack', args)
1003
1004
[email protected]5ca27692010-05-26 19:32:411005def CMDstatus(parser, args):
1006 """Show modification status for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191007 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1008 help='override deps for the specified (comma-separated) '
1009 'platform(s); \'all\' will process all deps_os '
1010 'references')
[email protected]5ca27692010-05-26 19:32:411011 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341012 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421013 if not client:
[email protected]0b6a0842010-06-15 14:34:191014 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421015 if options.verbose:
1016 # Print out the .gclient file. This is longer than if we just printed the
1017 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381018 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421019 return client.RunOnDeps('status', args)
1020
1021
[email protected]5ca27692010-05-26 19:32:411022@attr('epilog', """Examples:
[email protected]79692d62010-05-14 18:57:131023 gclient sync
1024 update files from SCM according to current configuration,
1025 *for modules which have changed since last update or sync*
1026 gclient sync --force
1027 update files from SCM according to current configuration, for
1028 all modules (useful for recovering files deleted from local copy)
1029 gclient sync --revision src@31000
1030 update src directory to r31000
[email protected]5ca27692010-05-26 19:32:411031""")
1032def CMDsync(parser, args):
1033 """Checkout/update all modules."""
[email protected]0b6a0842010-06-15 14:34:191034 parser.add_option('-f', '--force', action='store_true',
1035 help='force update even for unchanged modules')
1036 parser.add_option('-n', '--nohooks', action='store_true',
1037 help='don\'t run hooks after the update is complete')
1038 parser.add_option('-r', '--revision', action='append',
1039 dest='revisions', metavar='REV', default=[],
1040 help='Enforces revision/hash for the solutions with the '
1041 'format src@rev. The src@ part is optional and can be '
1042 'skipped. -r can be used multiple times when .gclient '
1043 'has multiple solutions configured and will work even '
[email protected]792ea882010-11-10 02:37:271044 'if the src@ part is skipped. Note that specifying '
1045 '--revision means your safesync_url gets ignored.')
[email protected]0b6a0842010-06-15 14:34:191046 parser.add_option('-H', '--head', action='store_true',
1047 help='skips any safesync_urls specified in '
1048 'configured solutions and sync to head instead')
1049 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
[email protected]28d14bd2010-11-11 20:37:091050 help='delete any dependency that have been removed from '
1051 'last sync as long as there is no local modification. '
1052 'Coupled with --force, it will remove them even with '
1053 'local modifications')
[email protected]0b6a0842010-06-15 14:34:191054 parser.add_option('-R', '--reset', action='store_true',
1055 help='resets any local changes before updating (git only)')
1056 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1057 help='override deps for the specified (comma-separated) '
1058 'platform(s); \'all\' will process all deps_os '
1059 'references')
1060 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1061 help='Skip svn up whenever possible by requesting '
1062 'actual HEAD revision from the repository')
[email protected]5ca27692010-05-26 19:32:411063 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341064 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421065
1066 if not client:
[email protected]0b6a0842010-06-15 14:34:191067 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421068
[email protected]307d1792010-05-31 20:03:131069 if options.revisions and options.head:
1070 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
[email protected]0b6a0842010-06-15 14:34:191071 print('Warning: you cannot use both --head and --revision')
[email protected]fb2b8eb2009-04-23 21:03:421072
1073 if options.verbose:
1074 # Print out the .gclient file. This is longer than if we just printed the
1075 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381076 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421077 return client.RunOnDeps('update', args)
1078
1079
[email protected]5ca27692010-05-26 19:32:411080def CMDupdate(parser, args):
[email protected]ddff62d2010-05-17 21:02:361081 """Alias for the sync command. Deprecated."""
[email protected]5ca27692010-05-26 19:32:411082 return CMDsync(parser, args)
[email protected]fb2b8eb2009-04-23 21:03:421083
[email protected]5ca27692010-05-26 19:32:411084def CMDdiff(parser, args):
1085 """Displays local diff for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191086 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1087 help='override deps for the specified (comma-separated) '
1088 'platform(s); \'all\' will process all deps_os '
1089 'references')
[email protected]5ca27692010-05-26 19:32:411090 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341091 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421092 if not client:
[email protected]0b6a0842010-06-15 14:34:191093 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421094 if options.verbose:
1095 # Print out the .gclient file. This is longer than if we just printed the
1096 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381097 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421098 return client.RunOnDeps('diff', args)
1099
1100
[email protected]5ca27692010-05-26 19:32:411101def CMDrevert(parser, args):
[email protected]28d14bd2010-11-11 20:37:091102 """Revert all modifications in every dependencies.
1103
1104 That's the nuclear option to get back to a 'clean' state. It removes anything
1105 that shows up in svn status."""
[email protected]0b6a0842010-06-15 14:34:191106 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1107 help='override deps for the specified (comma-separated) '
1108 'platform(s); \'all\' will process all deps_os '
1109 'references')
1110 parser.add_option('-n', '--nohooks', action='store_true',
1111 help='don\'t run hooks after the revert is complete')
[email protected]5ca27692010-05-26 19:32:411112 (options, args) = parser.parse_args(args)
1113 # --force is implied.
1114 options.force = True
[email protected]2806acc2009-05-15 12:33:341115 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421116 if not client:
[email protected]0b6a0842010-06-15 14:34:191117 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421118 return client.RunOnDeps('revert', args)
1119
1120
[email protected]5ca27692010-05-26 19:32:411121def CMDrunhooks(parser, args):
1122 """Runs hooks for files that have been modified in the local working copy."""
[email protected]0b6a0842010-06-15 14:34:191123 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1124 help='override deps for the specified (comma-separated) '
1125 'platform(s); \'all\' will process all deps_os '
1126 'references')
1127 parser.add_option('-f', '--force', action='store_true', default=True,
1128 help='Deprecated. No effect.')
[email protected]5ca27692010-05-26 19:32:411129 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341130 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421131 if not client:
[email protected]0b6a0842010-06-15 14:34:191132 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421133 if options.verbose:
1134 # Print out the .gclient file. This is longer than if we just printed the
1135 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381136 print(client.config_content)
[email protected]5df6a462009-08-28 18:52:261137 options.force = True
[email protected]5ca27692010-05-26 19:32:411138 options.nohooks = False
[email protected]fb2b8eb2009-04-23 21:03:421139 return client.RunOnDeps('runhooks', args)
1140
1141
[email protected]5ca27692010-05-26 19:32:411142def CMDrevinfo(parser, args):
[email protected]9eda4112010-06-11 18:56:101143 """Output revision info mapping for the client and its dependencies.
1144
[email protected]0b6a0842010-06-15 14:34:191145 This allows the capture of an overall 'revision' for the source tree that
[email protected]9eda4112010-06-11 18:56:101146 can be used to reproduce the same tree in the future. It is only useful for
[email protected]0b6a0842010-06-15 14:34:191147 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1148 number or a git hash. A git branch name isn't 'pinned' since the actual
[email protected]9eda4112010-06-11 18:56:101149 commit can change.
1150 """
1151 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1152 help='override deps for the specified (comma-separated) '
1153 'platform(s); \'all\' will process all deps_os '
1154 'references')
[email protected]b1e315f2010-08-11 18:44:501155 parser.add_option('-a', '--actual', action='store_true',
1156 help='gets the actual checked out revisions instead of the '
1157 'ones specified in the DEPS and .gclient files')
[email protected]9eda4112010-06-11 18:56:101158 parser.add_option('-s', '--snapshot', action='store_true',
1159 help='creates a snapshot .gclient file of the current '
[email protected]b1e315f2010-08-11 18:44:501160 'version of all repositories to reproduce the tree, '
1161 'implies -a')
[email protected]5ca27692010-05-26 19:32:411162 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341163 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421164 if not client:
[email protected]0b6a0842010-06-15 14:34:191165 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421166 client.PrintRevInfo()
[email protected]79692d62010-05-14 18:57:131167 return 0
[email protected]fb2b8eb2009-04-23 21:03:421168
1169
[email protected]5ca27692010-05-26 19:32:411170def Command(name):
1171 return getattr(sys.modules[__name__], 'CMD' + name, None)
1172
1173
1174def CMDhelp(parser, args):
1175 """Prints list of commands or help for a specific command."""
[email protected]6e29d572010-06-04 17:32:201176 (_, args) = parser.parse_args(args)
[email protected]ddff62d2010-05-17 21:02:361177 if len(args) == 1:
[email protected]5ca27692010-05-26 19:32:411178 return Main(args + ['--help'])
[email protected]ddff62d2010-05-17 21:02:361179 parser.print_help()
1180 return 0
1181
1182
[email protected]5ca27692010-05-26 19:32:411183def GenUsage(parser, command):
1184 """Modify an OptParse object with the function's documentation."""
1185 obj = Command(command)
1186 if command == 'help':
1187 command = '<command>'
1188 # OptParser.description prefer nicely non-formatted strings.
1189 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1190 usage = getattr(obj, 'usage', '')
1191 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1192 parser.epilog = getattr(obj, 'epilog', None)
[email protected]fb2b8eb2009-04-23 21:03:421193
1194
1195def Main(argv):
[email protected]5ca27692010-05-26 19:32:411196 """Doesn't parse the arguments here, just find the right subcommand to
1197 execute."""
[email protected]c3a15a22010-11-20 03:12:271198 if sys.hexversion < 0x02050000:
1199 print >> sys.stderr, (
1200 '\nYour python version is unsupported, please upgrade.\n')
[email protected]6e29d572010-06-04 17:32:201201 try:
[email protected]e0de9cb2010-09-17 15:07:141202 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1203 # operations. Python as a strong tendency to buffer sys.stdout.
1204 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
[email protected]4ed34182010-09-17 15:57:471205 # Make stdout annotated with the thread ids.
1206 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
[email protected]6e29d572010-06-04 17:32:201207 # Do it late so all commands are listed.
[email protected]b17b55b2010-11-03 14:42:371208 # Unused variable 'usage'
1209 # pylint: disable=W0612
[email protected]6e29d572010-06-04 17:32:201210 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1211 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1212 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1213 parser = optparse.OptionParser(version='%prog ' + __version__)
[email protected]fac9d3d2010-09-10 20:38:491214 parser.add_option('-j', '--jobs', default=1, type='int',
[email protected]3b84d4c2010-09-10 18:02:431215 help='Specify how many SCM commands can run in parallel; '
1216 'default=%default')
[email protected]f0fc9912010-06-11 17:57:331217 parser.add_option('-v', '--verbose', action='count', default=0,
1218 help='Produces additional output for diagnostics. Can be '
1219 'used up to three times for more logging info.')
1220 parser.add_option('--gclientfile', dest='config_filename',
1221 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1222 help='Specify an alternate %default file')
[email protected]6e29d572010-06-04 17:32:201223 # Integrate standard options processing.
1224 old_parser = parser.parse_args
1225 def Parse(args):
1226 (options, args) = old_parser(args)
[email protected]f0fc9912010-06-11 17:57:331227 level = None
[email protected]6e29d572010-06-04 17:32:201228 if options.verbose == 2:
[email protected]f0fc9912010-06-11 17:57:331229 level = logging.INFO
[email protected]6e29d572010-06-04 17:32:201230 elif options.verbose > 2:
[email protected]f0fc9912010-06-11 17:57:331231 level = logging.DEBUG
1232 logging.basicConfig(level=level,
1233 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1234 options.entries_filename = options.config_filename + '_entries'
[email protected]9e5317a2010-08-13 20:35:111235 if options.jobs < 1:
1236 parser.error('--jobs must be 1 or higher')
[email protected]e3216c62010-07-08 03:31:431237
1238 # These hacks need to die.
[email protected]6e29d572010-06-04 17:32:201239 if not hasattr(options, 'revisions'):
1240 # GClient.RunOnDeps expects it even if not applicable.
1241 options.revisions = []
1242 if not hasattr(options, 'head'):
1243 options.head = None
[email protected]f0fc9912010-06-11 17:57:331244 if not hasattr(options, 'nohooks'):
1245 options.nohooks = True
1246 if not hasattr(options, 'deps_os'):
1247 options.deps_os = None
[email protected]e3216c62010-07-08 03:31:431248 if not hasattr(options, 'manually_grab_svn_rev'):
1249 options.manually_grab_svn_rev = None
1250 if not hasattr(options, 'force'):
1251 options.force = None
[email protected]6e29d572010-06-04 17:32:201252 return (options, args)
1253 parser.parse_args = Parse
1254 # We don't want wordwrapping in epilog (usually examples)
1255 parser.format_epilog = lambda _: parser.epilog or ''
1256 if argv:
1257 command = Command(argv[0])
1258 if command:
[email protected]f0fc9912010-06-11 17:57:331259 # 'fix' the usage and the description now that we know the subcommand.
[email protected]6e29d572010-06-04 17:32:201260 GenUsage(parser, argv[0])
1261 return command(parser, argv[1:])
1262 # Not a known command. Default to help.
1263 GenUsage(parser, 'help')
1264 return CMDhelp(parser, argv)
[email protected]31cb48a2011-04-04 18:01:361265 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
[email protected]f0fc9912010-06-11 17:57:331266 print >> sys.stderr, 'Error: %s' % str(e)
[email protected]6e29d572010-06-04 17:32:201267 return 1
[email protected]fb2b8eb2009-04-23 21:03:421268
1269
[email protected]f0fc9912010-06-11 17:57:331270if '__main__' == __name__:
[email protected]35625c72011-03-23 17:34:021271 fix_encoding.fix_encoding()
[email protected]6e29d572010-06-04 17:32:201272 sys.exit(Main(sys.argv[1:]))
[email protected]fb2b8eb2009-04-23 21:03:421273
1274# vim: ts=2:sw=2:tw=80:et: